Module pythonConsole
[hide private]
[frames] | no frames]

Source Code for Module pythonConsole

  1  """Provides an interactive Python console which can be run from within NVDA. 
  2  To use, call L{initialize} to create a singleton instance of the console GUI. This can then be accessed externally as L{consoleUI}. 
  3  """ 
  4   
  5  import __builtin__ 
  6  import os 
  7  import code 
  8  import sys 
  9  import pydoc 
 10  import wx 
 11  from baseObject import AutoPropertyObject 
 12  import speech 
 13  import queueHandler 
 14  import api 
 15  import gui 
 16  from logHandler import log 
 17  import braille 
 18   
19 -class HelpCommand(object):
20 """ 21 Emulation of the 'help' command found in the Python interactive shell. 22 """ 23 24 _reprMessage=_("Type help(object) to get help about object.") 25
26 - def __repr__(self):
27 return self._reprMessage
28
29 - def __call__(self,*args,**kwargs):
30 return pydoc.help(*args,**kwargs)
31
32 -class ExitConsoleCommand(object):
33 """ 34 An object that can be used as an exit command that can close the console or print a friendly message for its repr. 35 """ 36
37 - def __init__(self, exitFunc):
38 self._exitFunc = exitFunc
39 40 _reprMessage=_("Type exit() to exit the console")
41 - def __repr__(self):
42 return self._reprMessage
43
44 - def __call__(self):
45 self._exitFunc()
46 47 #: The singleton Python console UI instance. 48 consoleUI = None 49
50 -class PythonConsole(code.InteractiveConsole, AutoPropertyObject):
51 """An interactive Python console for NVDA which directs output to supplied functions. 52 This is necessary for a Python console with input/output other than stdin/stdout/stderr. 53 Input is always received via the L{push} method. 54 This console handles redirection of stdout and stderr and prevents clobbering of the gettext "_" builtin. 55 The console's namespace is populated with useful modules 56 and can be updated with a snapshot of NVDA's state using L{updateNamespaceSnapshotVars}. 57 """ 58
59 - def __init__(self, outputFunc, setPromptFunc, exitFunc, echoFunc=None, **kwargs):
60 self._output = outputFunc 61 self._echo = echoFunc 62 self._setPrompt = setPromptFunc 63 64 #: The namespace available to the console. This can be updated externally. 65 #: @type: dict 66 # Populate with useful modules. 67 exitCmd = ExitConsoleCommand(exitFunc) 68 self.namespace = { 69 "help": HelpCommand(), 70 "exit": exitCmd, 71 "quit": exitCmd, 72 "sys": sys, 73 "os": os, 74 "wx": wx, 75 "log": log, 76 "api": api, 77 "queueHandler": queueHandler, 78 "speech": speech, 79 "braille": braille, 80 } 81 #: The variables last added to the namespace containing a snapshot of NVDA's state. 82 #: @type: dict 83 self._namespaceSnapshotVars = None 84 85 # Can't use super here because stupid code.InteractiveConsole doesn't sub-class object. Grrr! 86 code.InteractiveConsole.__init__(self, locals=self.namespace, **kwargs) 87 self.prompt = ">>>"
88
89 - def _set_prompt(self, prompt):
90 self._prompt = prompt 91 self._setPrompt(prompt)
92
93 - def _get_prompt(self):
94 return self._prompt
95
96 - def write(self, data):
97 self._output(data)
98
99 - def push(self, line):
100 if self._echo: 101 self._echo("%s %s\n" % (self.prompt, line)) 102 # Capture stdout/stderr output as well as code interaction. 103 stdout, stderr = sys.stdout, sys.stderr 104 sys.stdout = sys.stderr = self 105 # Prevent this from messing with the gettext "_" builtin. 106 saved_ = __builtin__._ 107 more = code.InteractiveConsole.push(self, line) 108 sys.stdout, sys.stderr = stdout, stderr 109 __builtin__._ = saved_ 110 self.prompt = "..." if more else ">>>" 111 return more
112
114 """Update the console namespace with a snapshot of NVDA's current state. 115 This creates/updates variables for the current focus, navigator object, etc. 116 """ 117 self._namespaceSnapshotVars = { 118 "focus": api.getFocusObject(), 119 # Copy the focus ancestor list, as it gets mutated once it is replaced in api.setFocusObject. 120 "focusAnc": list(api.getFocusAncestors()), 121 "fdl": api.getFocusDifferenceLevel(), 122 "fg": api.getForegroundObject(), 123 "nav": api.getNavigatorObject(), 124 "review":api.getReviewPosition(), 125 "mouse": api.getMouseObject(), 126 "brlRegions": braille.handler.buffer.regions, 127 } 128 self.namespace.update(self._namespaceSnapshotVars)
129
131 """Remove the variables from the console namespace containing the last snapshot of NVDA's state. 132 This removes the variables added by L{updateNamespaceSnapshotVars}. 133 """ 134 if not self._namespaceSnapshotVars: 135 return 136 for key in self._namespaceSnapshotVars: 137 try: 138 del self.namespace[key] 139 except KeyError: 140 pass 141 self._namespaceSnapshotVars = None
142
143 -class ConsoleUI(wx.Frame):
144 """The NVDA Python console GUI. 145 """ 146
147 - def __init__(self, parent):
148 super(ConsoleUI, self).__init__(parent, wx.ID_ANY, _("NVDA Python Console")) 149 self.Bind(wx.EVT_ACTIVATE, self.onActivate) 150 self.Bind(wx.EVT_CLOSE, self.onClose) 151 mainSizer = wx.BoxSizer(wx.VERTICAL) 152 self.outputCtrl = wx.TextCtrl(self, wx.ID_ANY, size=(500, 500), style=wx.TE_MULTILINE | wx.TE_READONLY|wx.TE_RICH) 153 self.outputCtrl.Bind(wx.EVT_CHAR, self.onOutputChar) 154 mainSizer.Add(self.outputCtrl, proportion=2, flag=wx.EXPAND) 155 inputSizer = wx.BoxSizer(wx.HORIZONTAL) 156 self.promptLabel = wx.StaticText(self, wx.ID_ANY) 157 inputSizer.Add(self.promptLabel, flag=wx.EXPAND) 158 self.inputCtrl = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_DONTWRAP | wx.TE_PROCESS_TAB) 159 self.inputCtrl.Bind(wx.EVT_CHAR, self.onInputChar) 160 inputSizer.Add(self.inputCtrl, proportion=1, flag=wx.EXPAND) 161 mainSizer.Add(inputSizer, proportion=1, flag=wx.EXPAND) 162 self.SetSizer(mainSizer) 163 mainSizer.Fit(self) 164 165 self.console = PythonConsole(outputFunc=self.output, echoFunc=self.echo, setPromptFunc=self.setPrompt, exitFunc=self.Close) 166 # Even the most recent line has a position in the history, so initialise with one blank line. 167 self.inputHistory = [""] 168 self.inputHistoryPos = 0
169
170 - def onActivate(self, evt):
171 if evt.GetActive(): 172 self.inputCtrl.SetFocus() 173 evt.Skip()
174
175 - def onClose(self, evt):
176 self.Hide() 177 self.console.removeNamespaceSnapshotVars()
178
179 - def output(self, data):
180 self.outputCtrl.write(data) 181 if data and not data.isspace(): 182 queueHandler.queueFunction(queueHandler.eventQueue, speech.speakText, data)
183
184 - def echo(self, data):
185 self.outputCtrl.write(data)
186
187 - def setPrompt(self, prompt):
188 self.promptLabel.SetLabel(prompt) 189 queueHandler.queueFunction(queueHandler.eventQueue, speech.speakText, prompt)
190
191 - def execute(self):
192 data = self.inputCtrl.GetValue() 193 self.console.push(data) 194 if data: 195 # Only add non-blank lines to history. 196 if len(self.inputHistory) > 1 and self.inputHistory[-2] == data: 197 # The previous line was the same and we don't want consecutive duplicates, so trash the most recent line. 198 del self.inputHistory[-1] 199 else: 200 # Update the content for the most recent line of history. 201 self.inputHistory[-1] = data 202 # Start with a new, blank line. 203 self.inputHistory.append("") 204 self.inputHistoryPos = len(self.inputHistory) - 1 205 self.inputCtrl.ChangeValue("")
206
207 - def historyMove(self, movement):
208 newIndex = self.inputHistoryPos + movement 209 if not (0 <= newIndex < len(self.inputHistory)): 210 # No more lines in this direction. 211 return False 212 # Update the content of the history at the current position. 213 self.inputHistory[self.inputHistoryPos] = self.inputCtrl.GetValue() 214 self.inputHistoryPos = newIndex 215 self.inputCtrl.ChangeValue(self.inputHistory[newIndex]) 216 self.inputCtrl.SetInsertionPointEnd() 217 return True
218
219 - def onInputChar(self, evt):
220 key = evt.GetKeyCode() 221 if key == wx.WXK_RETURN: 222 self.execute() 223 return 224 elif key in (wx.WXK_UP, wx.WXK_DOWN): 225 if self.historyMove(-1 if key == wx.WXK_UP else 1): 226 return 227 elif key == wx.WXK_F6: 228 self.outputCtrl.SetFocus() 229 return 230 elif key == wx.WXK_ESCAPE: 231 self.Close() 232 return 233 evt.Skip()
234
235 - def onOutputChar(self, evt):
236 key = evt.GetKeyCode() 237 if key == wx.WXK_F6: 238 self.inputCtrl.SetFocus() 239 return 240 elif key == wx.WXK_ESCAPE: 241 self.Close() 242 evt.Skip()
243
244 -def initialize():
245 """Initialize the NVDA Python console GUI. 246 This creates a singleton instance of the console GUI. This is accessible as L{consoleUI}. This may be manipulated externally. 247 """ 248 global consoleUI 249 consoleUI = ConsoleUI(gui.mainFrame)
250
251 -def activate():
252 """Activate the console GUI. 253 This shows the GUI and brings it to the foreground if possible. 254 @precondition: L{initialize} has been called. 255 """ 256 global consoleUI 257 consoleUI.Raise() 258 # There is a MAXIMIZE style which can be used on the frame at construction, but it doesn't seem to work the first time it is shown, 259 # probably because it was in the background. 260 # Therefore, explicitly maximise it here. 261 # This also ensures that it will be maximized whenever it is activated, even if the user restored/minimised it. 262 consoleUI.Maximize() 263 consoleUI.Show()
264