]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/shell.py
e14e6c6d4e47b9d3a90bd8a6b134f0ea46f40dc3
[wxWidgets.git] / wxPython / wxPython / lib / PyCrust / shell.py
1 """The PyCrust Shell is an interactive text control in which a user types in
2 commands to be sent to the interpreter. This particular shell is based on
3 wxPython's wxStyledTextCtrl. The latest files are always available at the
4 SourceForge project page at http://sourceforge.net/projects/pycrust/."""
5
6 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
7 __cvsid__ = "$Id$"
8 __date__ = "July 1, 2001"
9 __version__ = "$Revision$"[11:-2]
10
11 from wxPython.wx import *
12 from wxPython.stc import *
13 import keyword
14 import os
15 import sys
16 from version import VERSION
17
18
19 if wxPlatform == '__WXMSW__':
20 faces = { 'times' : 'Times New Roman',
21 'mono' : 'Courier New',
22 'helv' : 'Lucida Console',
23 'lucida' : 'Lucida Console',
24 'other' : 'Comic Sans MS',
25 'size' : 8,
26 'lnsize' : 7,
27 'backcol': '#FFFFFF',
28 }
29 else: # GTK
30 faces = { 'times' : 'Times',
31 'mono' : 'Courier',
32 'helv' : 'Helvetica',
33 'other' : 'new century schoolbook',
34 'size' : 12,
35 'lnsize' : 10,
36 'backcol': '#FFFFFF',
37 }
38
39
40 class Shell(wxStyledTextCtrl):
41 """PyCrust Shell based on wxStyledTextCtrl."""
42
43 name = 'PyCrust Shell'
44 revision = __version__
45
46 def __init__(self, parent, id=-1, pos=wxDefaultPosition, \
47 size=wxDefaultSize, style=wxCLIP_CHILDREN, introText='', \
48 locals=None, InterpClass=None, *args, **kwds):
49 """Create a PyCrust Shell instance."""
50 wxStyledTextCtrl.__init__(self, parent, id, pos, size, style)
51 # Import a default interpreter class if one isn't provided.
52 if InterpClass == None:
53 from interpreter import Interpreter
54 else:
55 Interpreter = InterpClass
56 # Create default locals so we have something interesting.
57 shellLocals = {'__name__': 'PyShell',
58 '__doc__': 'PyShell, The PyCrust Python Shell.',
59 '__version__': VERSION,
60 }
61 # Add the dictionary that was passed in.
62 if locals:
63 shellLocals.update(locals)
64 from pseudo import PseudoFileIn, PseudoFileOut, PseudoFileErr
65 self.interp = Interpreter(locals=shellLocals, \
66 rawin=self.readRaw, \
67 stdin=PseudoFileIn(self.readIn), \
68 stdout=PseudoFileOut(self.writeOut), \
69 stderr=PseudoFileErr(self.writeErr), \
70 *args, **kwds)
71 # Keep track of the most recent prompt starting and ending positions.
72 self.promptPos = [0, 0]
73 # Keep track of multi-line commands.
74 self.more = 0
75 # Create the command history. Commands are added into the front of
76 # the list (ie. at index 0) as they are entered. self.historyPos is
77 # the current position in the history; it gets incremented as you
78 # retrieve the previous command, decremented as you retrieve the next,
79 # and reset when you hit Enter. self.historyPos == -1 means you're on
80 # the current command, not in the history. self.tempCommand is
81 # storage space for whatever was on the last line when you first hit
82 # "Retrieve-Previous", so that the final "Retrieve-Next" will restore
83 # whatever was originally there. self.lastCommandRecalled remembers
84 # the index of the last command to be recalled from the history, so
85 # you can repeat a group of commands by going up-up-up-enter to find
86 # the first one in the group then down-enter-down-enter to recall each
87 # subsequent command. Also useful for multiline commands, in lieu of
88 # a proper implementation of those.
89 self.history = []
90 self.historyPos = -1
91 self.tempCommand = ''
92 self.lastCommandRecalled = -1
93 # Assign handlers for keyboard events.
94 EVT_KEY_DOWN(self, self.OnKeyDown)
95 EVT_CHAR(self, self.OnChar)
96 # Configure various defaults and user preferences.
97 self.config()
98 # Display the introductory banner information.
99 try: self.showIntro(introText)
100 except: pass
101 # Assign some pseudo keywords to the interpreter's namespace.
102 try: self.setBuiltinKeywords()
103 except: pass
104 # Add 'shell' to the interpreter's local namespace.
105 try: self.setLocalShell()
106 except: pass
107 # Do this last so the user has complete control over their
108 # environment. They can override anything they want.
109 try: self.execStartupScript(self.interp.startupScript)
110 except: pass
111
112 def destroy(self):
113 del self.interp
114
115 def config(self):
116 """Configure shell based on user preferences."""
117 self.SetMarginType(1, wxSTC_MARGIN_NUMBER)
118 self.SetMarginWidth(1, 40)
119
120 self.SetLexer(wxSTC_LEX_PYTHON)
121 self.SetKeyWords(0, ' '.join(keyword.kwlist))
122
123 self.setStyles(faces)
124 self.SetViewWhiteSpace(0)
125 self.SetTabWidth(4)
126 self.SetUseTabs(0)
127 # Do we want to automatically pop up command completion options?
128 self.autoComplete = 1
129 self.autoCompleteIncludeMagic = 1
130 self.autoCompleteIncludeSingle = 1
131 self.autoCompleteIncludeDouble = 1
132 self.autoCompleteCaseInsensitive = 1
133 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
134 # De we want to automatically pop up command argument help?
135 self.autoCallTip = 1
136 self.CallTipSetBackground(wxColour(255, 255, 232))
137
138 def showIntro(self, text=''):
139 """Display introductory text in the shell."""
140 if text:
141 if not text.endswith(os.linesep): text += os.linesep
142 self.write(text)
143 try:
144 self.write(self.interp.introText)
145 except AttributeError:
146 pass
147
148 def setBuiltinKeywords(self):
149 """Create pseudo keywords as part of builtins.
150
151 This is a rather clever hack that sets "close", "exit" and "quit"
152 to a PseudoKeyword object so that we can make them do what we want.
153 In this case what we want is to call our self.quit() method.
154 The user can type "close", "exit" or "quit" without the final parens.
155 """
156 ## POB: This is having some weird side-effects so I'm taking it out.
157 ## import __builtin__
158 ## from pseudo import PseudoKeyword
159 ## __builtin__.close = __builtin__.exit = __builtin__.quit = \
160 ## PseudoKeyword(self.quit)
161 import __builtin__
162 from pseudo import PseudoKeyword
163 __builtin__.close = __builtin__.exit = __builtin__.quit = \
164 'Click on the close button to leave the application.'
165
166 def quit(self):
167 """Quit the application."""
168
169 # XXX Good enough for now but later we want to send a close event.
170
171 # In the close event handler we can prompt to make sure they want to quit.
172 # Other applications, like PythonCard, may choose to hide rather than
173 # quit so we should just post the event and let the surrounding app
174 # decide what it wants to do.
175 self.write('Click on the close button to leave the application.')
176
177 def setLocalShell(self):
178 """Add 'shell' to locals."""
179 self.interp.locals['shell'] = self
180
181 def execStartupScript(self, startupScript):
182 """Execute the user's PYTHONSTARTUP script if they have one."""
183 if startupScript and os.path.isfile(startupScript):
184 startupText = 'Startup script executed: ' + startupScript
185 self.push('print %s;execfile(%s)' % \
186 (`startupText`, `startupScript`))
187 else:
188 self.push('')
189
190 def setStyles(self, faces):
191 """Configure font size, typeface and color for lexer."""
192
193 # Default style
194 self.StyleSetSpec(wxSTC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
195
196 self.StyleClearAll()
197
198 # Built in styles
199 self.StyleSetSpec(wxSTC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces)
200 self.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR, "face:%(mono)s" % faces)
201 self.StyleSetSpec(wxSTC_STYLE_BRACELIGHT, "fore:#0000FF,back:#FFFF88")
202 self.StyleSetSpec(wxSTC_STYLE_BRACEBAD, "fore:#FF0000,back:#FFFF88")
203
204 # Python styles
205 self.StyleSetSpec(wxSTC_P_DEFAULT, "face:%(mono)s" % faces)
206 self.StyleSetSpec(wxSTC_P_COMMENTLINE, "fore:#007F00,face:%(mono)s" % faces)
207 self.StyleSetSpec(wxSTC_P_NUMBER, "")
208 self.StyleSetSpec(wxSTC_P_STRING, "fore:#7F007F,face:%(mono)s" % faces)
209 self.StyleSetSpec(wxSTC_P_CHARACTER, "fore:#7F007F,face:%(mono)s" % faces)
210 self.StyleSetSpec(wxSTC_P_WORD, "fore:#00007F,bold")
211 self.StyleSetSpec(wxSTC_P_TRIPLE, "fore:#7F0000")
212 self.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE, "fore:#000033,back:#FFFFE8")
213 self.StyleSetSpec(wxSTC_P_CLASSNAME, "fore:#0000FF,bold")
214 self.StyleSetSpec(wxSTC_P_DEFNAME, "fore:#007F7F,bold")
215 self.StyleSetSpec(wxSTC_P_OPERATOR, "")
216 self.StyleSetSpec(wxSTC_P_IDENTIFIER, "")
217 self.StyleSetSpec(wxSTC_P_COMMENTBLOCK, "fore:#7F7F7F")
218 self.StyleSetSpec(wxSTC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces)
219
220 def OnKeyDown(self, event):
221 """Key down event handler.
222
223 The main goal here is to not allow modifications to previous
224 lines of text."""
225 key = event.KeyCode()
226 currpos = self.GetCurrentPos()
227 stoppos = self.promptPos[1]
228 # If the auto-complete window is up let it do its thing.
229 if self.AutoCompActive():
230 event.Skip()
231 # Control+UpArrow steps up through the history.
232 elif key == WXK_UP and event.ControlDown() \
233 and self.historyPos < len(self.history) - 1:
234 # Move to the end of the buffer.
235 endpos = self.GetTextLength()
236 self.SetCurrentPos(endpos)
237 # The first Control+Up stores the current command;
238 # Control+Down brings it back.
239 if self.historyPos == -1:
240 self.tempCommand = self.getCommand()
241 # Now replace the current line with the next one from the history.
242 self.historyPos = self.historyPos + 1
243 self.SetSelection(stoppos, endpos)
244 self.ReplaceSelection(self.history[self.historyPos])
245 # Control+DownArrow steps down through the history.
246 elif key == WXK_DOWN and event.ControlDown():
247 # Move to the end of the buffer.
248 endpos = self.GetTextLength()
249 self.SetCurrentPos(endpos)
250 # Are we at the bottom end of the history?
251 if self.historyPos == -1:
252 # Do we have a lastCommandRecalled stored?
253 if self.lastCommandRecalled >= 0:
254 # Replace the current line with the command after the
255 # last-recalled command (you'd think there should be a +1
256 # here but there isn't because the history was shuffled up
257 # by 1 after the previous command was recalled).
258 self.SetSelection(stoppos, endpos)
259 self.ReplaceSelection(self.history[self.lastCommandRecalled])
260 # We've now warped into middle of the history.
261 self.historyPos = self.lastCommandRecalled
262 self.lastCommandRecalled = -1
263 else:
264 # Fetch either the previous line from the history, or the saved
265 # command if we're back at the start.
266 self.historyPos = self.historyPos - 1
267 if self.historyPos == -1:
268 newText = self.tempCommand
269 else:
270 newText = self.history[self.historyPos]
271 # Replace the current line with the new text.
272 self.SetSelection(stoppos, endpos)
273 self.ReplaceSelection(newText)
274 # F8 on the last line does a search up the history for the text in
275 # front of the cursor.
276 elif key == WXK_F8 and self.GetCurrentLine() == self.GetLineCount()-1:
277 tempCommand = self.getCommand()
278 # The first F8 saves the current command, just like Control+Up.
279 if self.historyPos == -1:
280 self.tempCommand = tempCommand
281 # The text up to the cursor is what we search for.
282 searchText = tempCommand
283 numCharsAfterCursor = self.GetTextLength() - self.GetCurrentPos()
284 if numCharsAfterCursor > 0:
285 searchText = searchText[:-numCharsAfterCursor]
286 # Search upwards from the current history position and loop back
287 # to the beginning if we don't find anything.
288 for i in range(self.historyPos+1, len(self.history)) + \
289 range(self.historyPos):
290 command = self.history[i]
291 if command[:len(searchText)] == searchText:
292 # Replace the current line with the one we've found.
293 endpos = self.GetTextLength()
294 self.SetSelection(stoppos, endpos)
295 self.ReplaceSelection(command)
296 # Put the cursor back at the end of the search text.
297 pos = self.GetTextLength() - len(command) + len(searchText)
298 self.SetCurrentPos(pos)
299 self.SetAnchor(pos)
300 # We've now warped into middle of the history.
301 self.historyPos = i
302 self.lastCommandRecalled = -1
303 break
304 # Return is used to submit a command to the interpreter.
305 elif key == WXK_RETURN:
306 if self.CallTipActive: self.CallTipCancel()
307 self.processLine()
308 # Home needs to be aware of the prompt.
309 elif key == WXK_HOME:
310 if currpos >= stoppos:
311 self.SetCurrentPos(stoppos)
312 self.SetAnchor(stoppos)
313 else:
314 event.Skip()
315 # Basic navigation keys should work anywhere.
316 elif key in (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, \
317 WXK_PRIOR, WXK_NEXT):
318 event.Skip()
319 # Don't backspace over the latest prompt.
320 elif key == WXK_BACK:
321 if currpos > stoppos:
322 event.Skip()
323 # Only allow these keys after the latest prompt.
324 elif key in (WXK_TAB, WXK_DELETE):
325 if currpos >= stoppos:
326 event.Skip()
327 # Don't toggle between insert mode and overwrite mode.
328 elif key == WXK_INSERT:
329 pass
330 else:
331 event.Skip()
332
333 def OnChar(self, event):
334 """Keypress event handler.
335
336 The main goal here is to not allow modifications to previous
337 lines of text."""
338 key = event.KeyCode()
339 currpos = self.GetCurrentPos()
340 stoppos = self.promptPos[1]
341 if currpos >= stoppos:
342 if key == 46:
343 # "." The dot or period key activates auto completion.
344 # Get the command between the prompt and the cursor.
345 # Add a dot to the end of the command.
346 command = self.GetTextRange(stoppos, currpos) + '.'
347 self.write('.')
348 if self.autoComplete: self.autoCompleteShow(command)
349 elif key == 40:
350 # "(" The left paren activates a call tip and cancels
351 # an active auto completion.
352 if self.AutoCompActive(): self.AutoCompCancel()
353 # Get the command between the prompt and the cursor.
354 # Add the '(' to the end of the command.
355 command = self.GetTextRange(stoppos, currpos) + '('
356 self.write('(')
357 if self.autoCallTip: self.autoCallTipShow(command)
358 else:
359 # Allow the normal event handling to take place.
360 event.Skip()
361 else:
362 pass
363
364 def setStatusText(self, text):
365 """Display status information."""
366
367 # This method will most likely be replaced by the enclosing app
368 # to do something more interesting, like write to a status bar.
369 print text
370
371 def processLine(self):
372 """Process the line of text at which the user hit Enter."""
373
374 # The user hit ENTER and we need to decide what to do. They could be
375 # sitting on any line in the shell.
376
377 # Grab information about the current line.
378 thepos = self.GetCurrentPos()
379 theline = self.GetCurrentLine()
380 command = self.getCommand()
381 # Go to the very bottom of the text.
382 endpos = self.GetTextLength()
383 self.SetCurrentPos(endpos)
384 endline = self.GetCurrentLine()
385 # If they hit RETURN on the last line, execute the command.
386 if theline == endline:
387 # Store the last-recalled command; see the main comment for
388 # self.lastCommandRecalled.
389 if command != '':
390 self.lastCommandRecalled = self.historyPos
391 # Reset the history position.
392 self.historyPos = -1
393 # Insert this command into the history, unless it's a blank line
394 # or the same as the last command.
395 if command != '' \
396 and (len(self.history) == 0 or command != self.history[0]):
397 self.history.insert(0, command)
398 self.push(command)
399 # Otherwise, replace the last line with the new line.
400 else:
401 # If the new line contains a command (even an invalid one).
402 if command:
403 startpos = self.PositionFromLine(endline)
404 self.SetSelection(startpos, endpos)
405 self.ReplaceSelection('')
406 self.prompt()
407 self.write(command)
408 # Otherwise, put the cursor back where we started.
409 else:
410 self.SetCurrentPos(thepos)
411 self.SetAnchor(thepos)
412
413 def getCommand(self, text=None):
414 """Extract a command from text which may include a shell prompt.
415
416 The command may not necessarily be valid Python syntax."""
417 if not text:
418 text = self.GetCurLine()[0]
419 ## This is a hack due to a bug in the wxPython 2.3.2 beta. The following
420 ## two lines of code should go away once the bug has been fixed and the
421 ## line above should be restored.
422 ## self.write(' ')
423 ## text = self.GetCurLine()[0][:-1]
424 # XXX Need to extract real prompts here. Need to keep track of the
425 # prompt every time a command is issued.
426 ps1 = str(sys.ps1)
427 ps1size = len(ps1)
428 ps2 = str(sys.ps2)
429 ps2size = len(ps2)
430 text = text.rstrip()
431 # Strip the prompt off the front of text leaving just the command.
432 if text[:ps1size] == ps1:
433 command = text[ps1size:]
434 elif text[:ps2size] == ps2:
435 command = text[ps2size:]
436 else:
437 command = ''
438 return command
439
440 def push(self, command):
441 """Send command to the interpreter for execution."""
442 self.write(os.linesep)
443 self.more = self.interp.push(command)
444 self.prompt()
445 # Keep the undo feature from undoing previous responses. The only
446 # thing that can be undone is stuff typed after the prompt, before
447 # hitting enter. After they hit enter it becomes permanent.
448 self.EmptyUndoBuffer()
449
450 def write(self, text):
451 """Display text in the shell.
452
453 Replace line endings with OS-specific endings."""
454 lines = text.split('\r\n')
455 for l in range(len(lines)):
456 chunks = lines[l].split('\r')
457 for c in range(len(chunks)):
458 chunks[c] = os.linesep.join(chunks[c].split('\n'))
459 lines[l] = os.linesep.join(chunks)
460 text = os.linesep.join(lines)
461 self.AddText(text)
462 self.EnsureCaretVisible()
463 #self.ScrollToColumn(0)
464
465 def prompt(self):
466 """Display appropriate prompt for the context, either ps1 or ps2.
467
468 If this is a continuation line, autoindent as necessary."""
469 if self.more:
470 prompt = str(sys.ps2)
471 else:
472 prompt = str(sys.ps1)
473 pos = self.GetCurLine()[1]
474 if pos > 0: self.write(os.linesep)
475 self.promptPos[0] = self.GetCurrentPos()
476 self.write(prompt)
477 self.promptPos[1] = self.GetCurrentPos()
478 # XXX Add some autoindent magic here if more.
479 if self.more:
480 self.write('\t') # Temporary hack indentation.
481 self.EnsureCaretVisible()
482 self.ScrollToColumn(0)
483
484 def readIn(self):
485 """Replacement for stdin."""
486 prompt = 'Please enter your response:'
487 dialog = wxTextEntryDialog(None, prompt, \
488 'Input Dialog (Standard)', '')
489 try:
490 if dialog.ShowModal() == wxID_OK:
491 text = dialog.GetValue()
492 self.write(text + os.linesep)
493 return text
494 finally:
495 dialog.Destroy()
496 return ''
497
498 def readRaw(self, prompt='Please enter your response:'):
499 """Replacement for raw_input."""
500 dialog = wxTextEntryDialog(None, prompt, \
501 'Input Dialog (Raw)', '')
502 try:
503 if dialog.ShowModal() == wxID_OK:
504 text = dialog.GetValue()
505 return text
506 finally:
507 dialog.Destroy()
508 return ''
509
510 def ask(self, prompt='Please enter your response:'):
511 """Get response from the user."""
512 return raw_input(prompt=prompt)
513
514 def pause(self):
515 """Halt execution pending a response from the user."""
516 self.ask('Press enter to continue:')
517
518 def clear(self):
519 """Delete all text from the shell."""
520 self.ClearAll()
521
522 def run(self, command, prompt=1, verbose=1):
523 """Execute command within the shell as if it was typed in directly.
524 >>> shell.run('print "this"')
525 >>> print "this"
526 this
527 >>>
528 """
529 command = command.rstrip()
530 if prompt: self.prompt()
531 if verbose: self.write(command)
532 self.push(command)
533
534 def runfile(self, filename):
535 """Execute all commands in file as if they were typed into the shell."""
536 file = open(filename)
537 try:
538 self.prompt()
539 for command in file.readlines():
540 if command[:6] == 'shell.': # Run shell methods silently.
541 self.run(command, prompt=0, verbose=0)
542 else:
543 self.run(command, prompt=0, verbose=1)
544 finally:
545 file.close()
546
547 def autoCompleteShow(self, command):
548 """Display auto-completion popup list."""
549 list = self.interp.getAutoCompleteList(command, \
550 includeMagic=self.autoCompleteIncludeMagic, \
551 includeSingle=self.autoCompleteIncludeSingle, \
552 includeDouble=self.autoCompleteIncludeDouble)
553 if list:
554 options = ' '.join(list)
555 offset = 0
556 self.AutoCompShow(offset, options)
557
558 def autoCallTipShow(self, command):
559 """Display argument spec and docstring in a popup bubble thingie."""
560 if self.CallTipActive: self.CallTipCancel()
561 tip = self.interp.getCallTip(command)
562 if tip:
563 offset = self.GetCurrentPos()
564 self.CallTipShow(offset, tip)
565
566 def writeOut(self, text):
567 """Replacement for stdout."""
568 self.write(text)
569
570 def writeErr(self, text):
571 """Replacement for stderr."""
572 self.write(text)
573
574 def CanCut(self):
575 """Return true if text is selected and can be cut."""
576 return self.GetSelectionStart() != self.GetSelectionEnd()
577
578 def CanCopy(self):
579 """Return true if text is selected and can be copied."""
580 return self.GetSelectionStart() != self.GetSelectionEnd()
581
582
583 wxID_SELECTALL = NewId() # This *should* be defined by wxPython.
584 ID_AUTOCOMP = NewId()
585 ID_AUTOCOMP_SHOW = NewId()
586 ID_AUTOCOMP_INCLUDE_MAGIC = NewId()
587 ID_AUTOCOMP_INCLUDE_SINGLE = NewId()
588 ID_AUTOCOMP_INCLUDE_DOUBLE = NewId()
589 ID_CALLTIPS = NewId()
590 ID_CALLTIPS_SHOW = NewId()
591
592
593 class ShellFrame(wxFrame):
594 """Frame containing the PyCrust shell component."""
595
596 name = 'PyCrust Shell Frame'
597 revision = __version__
598
599 def __init__(self, parent=None, id=-1, title='PyShell', \
600 pos=wxDefaultPosition, size=wxDefaultSize, \
601 style=wxDEFAULT_FRAME_STYLE, locals=None, \
602 InterpClass=None, *args, **kwds):
603 """Create a PyCrust ShellFrame instance."""
604 wxFrame.__init__(self, parent, id, title, pos, size, style)
605 intro = 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
606 self.CreateStatusBar()
607 self.SetStatusText(intro)
608 if wxPlatform == '__WXMSW__':
609 icon = wxIcon('PyCrust.ico', wxBITMAP_TYPE_ICO)
610 self.SetIcon(icon)
611 self.createMenus()
612 self.shell = Shell(parent=self, id=-1, introText=intro, \
613 locals=locals, InterpClass=InterpClass, \
614 *args, **kwds)
615 # Override the shell so that status messages go to the status bar.
616 self.shell.setStatusText = self.SetStatusText
617
618 def createMenus(self):
619 m = self.fileMenu = wxMenu()
620 m.AppendSeparator()
621 m.Append(wxID_EXIT, 'E&xit', 'Exit PyCrust')
622
623 m = self.editMenu = wxMenu()
624 m.Append(wxID_UNDO, '&Undo \tCtrl+Z', 'Undo the last action')
625 m.Append(wxID_REDO, '&Redo \tCtrl+Y', 'Redo the last undone action')
626 m.AppendSeparator()
627 m.Append(wxID_CUT, 'Cu&t \tCtrl+X', 'Cut the selection')
628 m.Append(wxID_COPY, '&Copy \tCtrl+C', 'Copy the selection')
629 m.Append(wxID_PASTE, '&Paste \tCtrl+V', 'Paste')
630 m.AppendSeparator()
631 m.Append(wxID_CLEAR, 'Cle&ar \tDel', 'Delete the selection')
632 m.Append(wxID_SELECTALL, 'Select A&ll \tCtrl+A', 'Select all text')
633
634 m = self.autocompMenu = wxMenu()
635 m.Append(ID_AUTOCOMP_SHOW, 'Show Auto Completion', \
636 'Show auto completion during dot syntax', \
637 checkable=1)
638 m.Append(ID_AUTOCOMP_INCLUDE_MAGIC, 'Include Magic Attributes', \
639 'Include attributes visible to __getattr__ and __setattr__', \
640 checkable=1)
641 m.Append(ID_AUTOCOMP_INCLUDE_SINGLE, 'Include Single Underscores', \
642 'Include attibutes prefixed by a single underscore', \
643 checkable=1)
644 m.Append(ID_AUTOCOMP_INCLUDE_DOUBLE, 'Include Double Underscores', \
645 'Include attibutes prefixed by a double underscore', \
646 checkable=1)
647
648 m = self.calltipsMenu = wxMenu()
649 m.Append(ID_CALLTIPS_SHOW, 'Show Call Tips', \
650 'Show call tips with argument specifications', checkable=1)
651
652 m = self.optionsMenu = wxMenu()
653 m.AppendMenu(ID_AUTOCOMP, '&Auto Completion', self.autocompMenu, \
654 'Auto Completion Options')
655 m.AppendMenu(ID_CALLTIPS, '&Call Tips', self.calltipsMenu, \
656 'Call Tip Options')
657
658 m = self.helpMenu = wxMenu()
659 m.AppendSeparator()
660 m.Append(wxID_ABOUT, '&About...', 'About PyCrust')
661
662 b = self.menuBar = wxMenuBar()
663 b.Append(self.fileMenu, '&File')
664 b.Append(self.editMenu, '&Edit')
665 b.Append(self.optionsMenu, '&Options')
666 b.Append(self.helpMenu, '&Help')
667 self.SetMenuBar(b)
668
669 EVT_MENU(self, wxID_EXIT, self.OnExit)
670 EVT_MENU(self, wxID_UNDO, self.OnUndo)
671 EVT_MENU(self, wxID_REDO, self.OnRedo)
672 EVT_MENU(self, wxID_CUT, self.OnCut)
673 EVT_MENU(self, wxID_COPY, self.OnCopy)
674 EVT_MENU(self, wxID_PASTE, self.OnPaste)
675 EVT_MENU(self, wxID_CLEAR, self.OnClear)
676 EVT_MENU(self, wxID_SELECTALL, self.OnSelectAll)
677 EVT_MENU(self, wxID_ABOUT, self.OnAbout)
678 EVT_MENU(self, ID_AUTOCOMP_SHOW, \
679 self.OnAutoCompleteShow)
680 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_MAGIC, \
681 self.OnAutoCompleteIncludeMagic)
682 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_SINGLE, \
683 self.OnAutoCompleteIncludeSingle)
684 EVT_MENU(self, ID_AUTOCOMP_INCLUDE_DOUBLE, \
685 self.OnAutoCompleteIncludeDouble)
686 EVT_MENU(self, ID_CALLTIPS_SHOW, \
687 self.OnCallTipsShow)
688
689 EVT_UPDATE_UI(self, wxID_UNDO, self.OnUpdateMenu)
690 EVT_UPDATE_UI(self, wxID_REDO, self.OnUpdateMenu)
691 EVT_UPDATE_UI(self, wxID_CUT, self.OnUpdateMenu)
692 EVT_UPDATE_UI(self, wxID_COPY, self.OnUpdateMenu)
693 EVT_UPDATE_UI(self, wxID_PASTE, self.OnUpdateMenu)
694 EVT_UPDATE_UI(self, wxID_CLEAR, self.OnUpdateMenu)
695 EVT_UPDATE_UI(self, ID_AUTOCOMP_SHOW, self.OnUpdateMenu)
696 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_MAGIC, self.OnUpdateMenu)
697 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_SINGLE, self.OnUpdateMenu)
698 EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_DOUBLE, self.OnUpdateMenu)
699 EVT_UPDATE_UI(self, ID_CALLTIPS_SHOW, self.OnUpdateMenu)
700
701 def OnExit(self, event):
702 self.Close(true)
703
704 def OnUndo(self, event):
705 self.shell.Undo()
706
707 def OnRedo(self, event):
708 self.shell.Redo()
709
710 def OnCut(self, event):
711 self.shell.Cut()
712
713 def OnCopy(self, event):
714 self.shell.Copy()
715
716 def OnPaste(self, event):
717 self.shell.Paste()
718
719 def OnClear(self, event):
720 self.shell.Clear()
721
722 def OnSelectAll(self, event):
723 self.shell.SelectAll()
724
725 def OnAbout(self, event):
726 """Display an About PyCrust window."""
727 import sys
728 title = 'About PyCrust'
729 text = 'PyCrust %s\n\n' % VERSION + \
730 'Yet another Python shell, only flakier.\n\n' + \
731 'Half-baked by Patrick K. O\'Brien,\n' + \
732 'the other half is still in the oven.\n\n' + \
733 'Shell Revision: %s\n' % self.shell.revision + \
734 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \
735 'Python Version: %s\n' % sys.version.split()[0] + \
736 'wxPython Version: %s\n' % wx.__version__ + \
737 'Platform: %s\n' % sys.platform
738 dialog = wxMessageDialog(self, text, title, wxOK | wxICON_INFORMATION)
739 dialog.ShowModal()
740 dialog.Destroy()
741
742 def OnAutoCompleteShow(self, event):
743 self.shell.autoComplete = event.IsChecked()
744
745 def OnAutoCompleteIncludeMagic(self, event):
746 self.shell.autoCompleteIncludeMagic = event.IsChecked()
747
748 def OnAutoCompleteIncludeSingle(self, event):
749 self.shell.autoCompleteIncludeSingle = event.IsChecked()
750
751 def OnAutoCompleteIncludeDouble(self, event):
752 self.shell.autoCompleteIncludeDouble = event.IsChecked()
753
754 def OnCallTipsShow(self, event):
755 self.shell.autoCallTip = event.IsChecked()
756
757 def OnUpdateMenu(self, event):
758 """Update menu items based on current status."""
759 id = event.GetId()
760 if id == wxID_UNDO:
761 event.Enable(self.shell.CanUndo())
762 elif id == wxID_REDO:
763 event.Enable(self.shell.CanRedo())
764 elif id == wxID_CUT:
765 event.Enable(self.shell.CanCut())
766 elif id == wxID_COPY:
767 event.Enable(self.shell.CanCopy())
768 elif id == wxID_PASTE:
769 event.Enable(self.shell.CanPaste())
770 elif id == wxID_CLEAR:
771 event.Enable(self.shell.CanCut())
772 elif id == ID_AUTOCOMP_SHOW:
773 event.Check(self.shell.autoComplete)
774 elif id == ID_AUTOCOMP_INCLUDE_MAGIC:
775 event.Check(self.shell.autoCompleteIncludeMagic)
776 elif id == ID_AUTOCOMP_INCLUDE_SINGLE:
777 event.Check(self.shell.autoCompleteIncludeSingle)
778 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE:
779 event.Check(self.shell.autoCompleteIncludeDouble)
780 elif id == ID_CALLTIPS_SHOW:
781 event.Check(self.shell.autoCallTip)
782
783