##// END OF EJS Templates
Merging Laurent's WX branch, reviewed by Gael....
Fernando Perez -
r1862:321357e1 merge
parent child Browse files
Show More
@@ -62,11 +62,10 b' class _Helper(object):'
62 62 ##############################################################################
63 63 class _CodeExecutor(ThreadEx):
64 64 ''' Thread that execute ipython code '''
65 def __init__(self, instance, after):
65 def __init__(self, instance):
66 66 ThreadEx.__init__(self)
67 67 self.instance = instance
68 self._afterExecute = after
69
68
70 69 def run(self):
71 70 '''Thread main loop'''
72 71 try:
@@ -74,7 +73,7 b' class _CodeExecutor(ThreadEx):'
74 73 self.instance._help_text = None
75 74 self.instance._execute()
76 75 # used for uper class to generate event after execution
77 self._afterExecute()
76 self.instance._after_execute()
78 77
79 78 except KeyboardInterrupt:
80 79 pass
@@ -114,8 +113,7 b' class NonBlockingIPShell(object):'
114 113 '''
115 114 #ipython0 initialisation
116 115 self._IP = None
117 self._term = None
118 self.initIpython0(argv, user_ns, user_global_ns,
116 self.init_ipython0(argv, user_ns, user_global_ns,
119 117 cin, cout, cerr,
120 118 ask_exit_handler)
121 119
@@ -127,33 +125,32 b' class NonBlockingIPShell(object):'
127 125
128 126 #thread working vars
129 127 self._line_to_execute = ''
130
128 self._threading = True
129
131 130 #vars that will be checked by GUI loop to handle thread states...
132 131 #will be replaced later by PostEvent GUI funtions...
133 132 self._doc_text = None
134 133 self._help_text = None
135 134 self._add_button = None
136 135
137 def initIpython0(self, argv=[], user_ns={}, user_global_ns=None,
136 def init_ipython0(self, argv=[], user_ns={}, user_global_ns=None,
138 137 cin=None, cout=None, cerr=None,
139 138 ask_exit_handler=None):
140 ''' Initialize an ithon0 instance '''
139 ''' Initialize an ipython0 instance '''
141 140
142 #first we redefine in/out/error functions of IPython
141 #first we redefine in/out/error functions of IPython
142 #BUG: we've got a limitation form ipython0 there
143 #only one instance can be instanciated else tehre will be
144 #cin/cout/cerr clash...
143 145 if cin:
144 IPython.Shell.Term.cin = cin
146 IPython.genutils.Term.cin = cin
145 147 if cout:
146 IPython.Shell.Term.cout = cout
148 IPython.genutils.Term.cout = cout
147 149 if cerr:
148 IPython.Shell.Term.cerr = cerr
150 IPython.genutils.Term.cerr = cerr
149 151
150 # This is to get rid of the blockage that accurs during
151 # IPython.Shell.InteractiveShell.user_setup()
152 IPython.iplib.raw_input = lambda x: None
153
154 self._term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr)
155
156 152 excepthook = sys.excepthook
153
157 154 #Hack to save sys.displayhook, because ipython seems to overwrite it...
158 155 self.sys_displayhook_ori = sys.displayhook
159 156
@@ -163,7 +160,8 b' class NonBlockingIPShell(object):'
163 160 embedded=True,
164 161 shell_class=IPython.Shell.InteractiveShell)
165 162
166 #we restore sys.displayhook
163 #we save ipython0 displayhook and we restore sys.displayhook
164 self.displayhook = sys.displayhook
167 165 sys.displayhook = self.sys_displayhook_ori
168 166
169 167 #we replace IPython default encoding by wx locale encoding
@@ -173,11 +171,12 b' class NonBlockingIPShell(object):'
173 171 #we replace the ipython default pager by our pager
174 172 self._IP.set_hook('show_in_pager', self._pager)
175 173
176 #we replace the ipython default shell command caller by our shell handler
174 #we replace the ipython default shell command caller
175 #by our shell handler
177 176 self._IP.set_hook('shell_hook', self._shell)
178 177
179 178 #we replace the ipython default input command caller by our method
180 IPython.iplib.raw_input_original = self._raw_input
179 IPython.iplib.raw_input_original = self._raw_input_original
181 180 #we replace the ipython default exit command by our method
182 181 self._IP.exit = ask_exit_handler
183 182 #we replace the help command
@@ -186,26 +185,68 b' class NonBlockingIPShell(object):'
186 185 #we disable cpase magic... until we found a way to use it properly.
187 186 #import IPython.ipapi
188 187 ip = IPython.ipapi.get()
189 def bypassMagic(self, arg):
188 def bypass_magic(self, arg):
190 189 print '%this magic is currently disabled.'
191 ip.expose_magic('cpaste', bypassMagic)
190 ip.expose_magic('cpaste', bypass_magic)
191
192 import __builtin__
193 __builtin__.raw_input = self._raw_input
192 194
193 195 sys.excepthook = excepthook
194 196
195 #----------------------- Thread management section ----------------------
196 def doExecute(self, line):
197 #----------------------- Thread management section ----------------------
198 def do_execute(self, line):
197 199 """
198 200 Tell the thread to process the 'line' command
199 201 """
200 202
201 203 self._line_to_execute = line
202 #we launch the ipython line execution in a thread to make it interruptible
203 #with include it in self namespace to be able to call ce.raise_exc(KeyboardInterrupt)
204 self.ce = _CodeExecutor(self, self._afterExecute)
205 self.ce.start()
206
207 #----------------------- IPython management section ----------------------
208 def getDocText(self):
204
205 if self._threading:
206 #we launch the ipython line execution in a thread to make it
207 #interruptible with include it in self namespace to be able
208 #to call ce.raise_exc(KeyboardInterrupt)
209 self.ce = _CodeExecutor(self)
210 self.ce.start()
211 else:
212 try:
213 self._doc_text = None
214 self._help_text = None
215 self._execute()
216 # used for uper class to generate event after execution
217 self._after_execute()
218
219 except KeyboardInterrupt:
220 pass
221
222 #----------------------- IPython management section ----------------------
223 def get_threading(self):
224 """
225 Returns threading status, is set to True, then each command sent to
226 the interpreter will be executed in a separated thread allowing,
227 for example, breaking a long running commands.
228 Disallowing it, permits better compatibilty with instance that is embedding
229 IPython instance.
230
231 @return: Execution method
232 @rtype: bool
233 """
234 return self._threading
235
236 def set_threading(self, state):
237 """
238 Sets threading state, if set to True, then each command sent to
239 the interpreter will be executed in a separated thread allowing,
240 for example, breaking a long running commands.
241 Disallowing it, permits better compatibilty with instance that is embedding
242 IPython instance.
243
244 @param state: Sets threading state
245 @type bool
246 """
247 self._threading = state
248
249 def get_doc_text(self):
209 250 """
210 251 Returns the output of the processing that need to be paged (if any)
211 252
@@ -214,7 +255,7 b' class NonBlockingIPShell(object):'
214 255 """
215 256 return self._doc_text
216 257
217 def getHelpText(self):
258 def get_help_text(self):
218 259 """
219 260 Returns the output of the processing that need to be paged via help pager(if any)
220 261
@@ -223,7 +264,7 b' class NonBlockingIPShell(object):'
223 264 """
224 265 return self._help_text
225 266
226 def getBanner(self):
267 def get_banner(self):
227 268 """
228 269 Returns the IPython banner for useful info on IPython instance
229 270
@@ -232,7 +273,7 b' class NonBlockingIPShell(object):'
232 273 """
233 274 return self._IP.BANNER
234 275
235 def getPromptCount(self):
276 def get_prompt_count(self):
236 277 """
237 278 Returns the prompt number.
238 279 Each time a user execute a line in the IPython shell the prompt count is increased
@@ -242,7 +283,7 b' class NonBlockingIPShell(object):'
242 283 """
243 284 return self._IP.outputcache.prompt_count
244 285
245 def getPrompt(self):
286 def get_prompt(self):
246 287 """
247 288 Returns current prompt inside IPython instance
248 289 (Can be In [...]: ot ...:)
@@ -252,7 +293,7 b' class NonBlockingIPShell(object):'
252 293 """
253 294 return self._prompt
254 295
255 def getIndentation(self):
296 def get_indentation(self):
256 297 """
257 298 Returns the current indentation level
258 299 Usefull to put the caret at the good start position if we want to do autoindentation.
@@ -262,7 +303,7 b' class NonBlockingIPShell(object):'
262 303 """
263 304 return self._IP.indent_current_nsp
264 305
265 def updateNamespace(self, ns_dict):
306 def update_namespace(self, ns_dict):
266 307 '''
267 308 Add the current dictionary to the shell namespace.
268 309
@@ -286,7 +327,7 b' class NonBlockingIPShell(object):'
286 327 possibilities = self._IP.complete(split_line[-1])
287 328 if possibilities:
288 329
289 def _commonPrefix(str1, str2):
330 def _common_prefix(str1, str2):
290 331 '''
291 332 Reduction function. returns common prefix of two given strings.
292 333
@@ -302,13 +343,13 b' class NonBlockingIPShell(object):'
302 343 if not str2.startswith(str1[:i+1]):
303 344 return str1[:i]
304 345 return str1
305 common_prefix = reduce(_commonPrefix, possibilities)
346 common_prefix = reduce(_common_prefix, possibilities)
306 347 completed = line[:-len(split_line[-1])]+common_prefix
307 348 else:
308 349 completed = line
309 350 return completed, possibilities
310 351
311 def historyBack(self):
352 def history_back(self):
312 353 '''
313 354 Provides one history command back.
314 355
@@ -320,10 +361,10 b' class NonBlockingIPShell(object):'
320 361 while((history == '' or history == '\n') and self._history_level >0):
321 362 if self._history_level >= 1:
322 363 self._history_level -= 1
323 history = self._getHistory()
364 history = self._get_history()
324 365 return history
325 366
326 def historyForward(self):
367 def history_forward(self):
327 368 '''
328 369 Provides one history command forward.
329 370
@@ -333,38 +374,38 b' class NonBlockingIPShell(object):'
333 374 history = ''
334 375 #the below while loop is used to suppress empty history lines
335 376 while((history == '' or history == '\n') \
336 and self._history_level <= self._getHistoryMaxIndex()):
337 if self._history_level < self._getHistoryMaxIndex():
377 and self._history_level <= self._get_history_max_index()):
378 if self._history_level < self._get_history_max_index():
338 379 self._history_level += 1
339 history = self._getHistory()
380 history = self._get_history()
340 381 else:
341 if self._history_level == self._getHistoryMaxIndex():
342 history = self._getHistory()
382 if self._history_level == self._get_history_max_index():
383 history = self._get_history()
343 384 self._history_level += 1
344 385 else:
345 386 history = ''
346 387 return history
347 388
348 def initHistoryIndex(self):
389 def init_history_index(self):
349 390 '''
350 391 set history to last command entered
351 392 '''
352 self._history_level = self._getHistoryMaxIndex()+1
393 self._history_level = self._get_history_max_index()+1
353 394
354 395 #----------------------- IPython PRIVATE management section --------------
355 def _afterExecute(self):
396 def _after_execute(self):
356 397 '''
357 398 Can be redefined to generate post event after excution is done
358 399 '''
359 400 pass
360 401
361 #def _askExit(self):
362 # '''
363 # Can be redefined to generate post event to exit the Ipython shell
364 # '''
365 # pass
402 def _ask_exit(self):
403 '''
404 Can be redefined to generate post event to exit the Ipython shell
405 '''
406 pass
366 407
367 def _getHistoryMaxIndex(self):
408 def _get_history_max_index(self):
368 409 '''
369 410 returns the max length of the history buffer
370 411
@@ -373,7 +414,7 b' class NonBlockingIPShell(object):'
373 414 '''
374 415 return len(self._IP.input_hist_raw)-1
375 416
376 def _getHistory(self):
417 def _get_history(self):
377 418 '''
378 419 Get's the command string of the current history level.
379 420
@@ -388,7 +429,7 b' class NonBlockingIPShell(object):'
388 429 This function is used as a callback replacment to IPython help pager function
389 430
390 431 It puts the 'text' value inside the self._help_text string that can be retrived via
391 getHelpText function.
432 get_help_text function.
392 433 '''
393 434 if self._help_text == None:
394 435 self._help_text = text
@@ -400,11 +441,11 b' class NonBlockingIPShell(object):'
400 441 This function is used as a callback replacment to IPython pager function
401 442
402 443 It puts the 'text' value inside the self._doc_text string that can be retrived via
403 getDocText function.
444 get_doc_text function.
404 445 '''
405 446 self._doc_text = text
406 447
407 def _raw_input(self, prompt=''):
448 def _raw_input_original(self, prompt=''):
408 449 '''
409 450 Custom raw_input() replacement. Get's current line from console buffer.
410 451
@@ -416,13 +457,21 b' class NonBlockingIPShell(object):'
416 457 '''
417 458 return self._line_to_execute
418 459
460 def _raw_input(self, prompt=''):
461 """ A replacement from python's raw_input.
462 """
463 raise NotImplementedError
464
419 465 def _execute(self):
420 466 '''
421 467 Executes the current line provided by the shell object.
422 468 '''
469
423 470 orig_stdout = sys.stdout
424 471 sys.stdout = IPython.Shell.Term.cout
425
472 #self.sys_displayhook_ori = sys.displayhook
473 #sys.displayhook = self.displayhook
474
426 475 try:
427 476 line = self._IP.raw_input(None, self._iter_more)
428 477 if self._IP.autoindent:
@@ -440,8 +489,10 b' class NonBlockingIPShell(object):'
440 489 except:
441 490 self._IP.showtraceback()
442 491 else:
492 self._IP.write(str(self._IP.outputcache.prompt_out).strip())
443 493 self._iter_more = self._IP.push(line)
444 if (self._IP.SyntaxTB.last_syntax_error and self._IP.rc.autoedit_syntax):
494 if (self._IP.SyntaxTB.last_syntax_error and \
495 self._IP.rc.autoedit_syntax):
445 496 self._IP.edit_syntax_error()
446 497 if self._iter_more:
447 498 self._prompt = str(self._IP.outputcache.prompt2).strip()
@@ -450,8 +501,10 b' class NonBlockingIPShell(object):'
450 501 else:
451 502 self._prompt = str(self._IP.outputcache.prompt1).strip()
452 503 self._IP.indent_current_nsp = 0 #we set indentation to 0
504
453 505 sys.stdout = orig_stdout
454
506 #sys.displayhook = self.sys_displayhook_ori
507
455 508 def _shell(self, ip, cmd):
456 509 '''
457 510 Replacement method to allow shell commands without them blocking.
@@ -462,7 +515,8 b' class NonBlockingIPShell(object):'
462 515 @type cmd: string
463 516 '''
464 517 stdin, stdout = os.popen4(cmd)
465 result = stdout.read().decode('cp437').encode(locale.getpreferredencoding())
518 result = stdout.read().decode('cp437').\
519 encode(locale.getpreferredencoding())
466 520 #we use print command because the shell command is called
467 521 #inside IPython instance and thus is redirected to thread cout
468 522 #"\x01\x1b[1;36m\x02" <-- add colour to the text...
@@ -29,17 +29,21 b' class IPythonHistoryPanel(wx.Panel):'
29 29 self.filter_magic = wx.CheckBox(self, -1, "%: Magic keys")
30 30
31 31 self.options={'filter_empty':{'value':'True',
32 'checkbox':self.filter_empty,'True':True,'False':False,
33 'setfunc':lambda x:None},
32 'checkbox':self.filter_empty, \
33 'True':True,'False':False,
34 'setfunc':lambda x:None},
34 35 'filter_doc':{'value':'True',
35 'checkbox':self.filter_doc,'True':True,'False':False,
36 'setfunc':lambda x:None},
36 'checkbox':self.filter_doc, \
37 'True':True,'False':False,
38 'setfunc':lambda x:None},
37 39 'filter_cmd':{'value':'True',
38 'checkbox':self.filter_cmd,'True':True,'False':False,
39 'setfunc':lambda x:None},
40 'checkbox':self.filter_cmd, \
41 'True':True,'False':False,
42 'setfunc':lambda x:None},
40 43 'filter_magic':{'value':'True',
41 'checkbox':self.filter_magic,'True':True,'False':False,
42 'setfunc':lambda x:None},
44 'checkbox':self.filter_magic, \
45 'True':True,'False':False,
46 'setfunc':lambda x:None},
43 47 }
44 48 self.reloadOptions(self.options)
45 49
@@ -199,51 +203,81 b' class PythonSTC(stc.StyledTextCtrl):'
199 203 self.SetLayoutCache(stc.STC_CACHE_PAGE)
200 204
201 205 # Setup a margin to hold fold markers
202 #self.SetFoldFlags(16) ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
206 #self.SetFoldFlags(16)
207 ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER?
203 208 self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
204 209 self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
205 210 self.SetMarginSensitive(2, True)
206 211 self.SetMarginWidth(2, 12)
207 212
208 213 if self.fold_symbols == 0:
209 # Arrow pointing right for contracted folders, arrow pointing down for expanded
210 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "black", "black")
211 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "black", "black")
212 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "black", "black")
213 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "black", "black")
214 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
215 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
216 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
214 # Arrow pointing right for contracted folders,
215 # arrow pointing down for expanded
216 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
217 stc.STC_MARK_ARROWDOWN, "black", "black")
218 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
219 stc.STC_MARK_ARROW, "black", "black")
220 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
221 stc.STC_MARK_EMPTY, "black", "black")
222 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
223 stc.STC_MARK_EMPTY, "black", "black")
224 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
225 stc.STC_MARK_EMPTY, "white", "black")
226 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
227 stc.STC_MARK_EMPTY, "white", "black")
228 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
229 stc.STC_MARK_EMPTY, "white", "black")
217 230
218 231 elif self.fold_symbols == 1:
219 232 # Plus for contracted folders, minus for expanded
220 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_MINUS, "white", "black")
221 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_PLUS, "white", "black")
222 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_EMPTY, "white", "black")
223 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_EMPTY, "white", "black")
224 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_EMPTY, "white", "black")
225 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
226 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")
233 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
234 stc.STC_MARK_MINUS, "white", "black")
235 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
236 stc.STC_MARK_PLUS, "white", "black")
237 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
238 stc.STC_MARK_EMPTY, "white", "black")
239 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
240 stc.STC_MARK_EMPTY, "white", "black")
241 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
242 stc.STC_MARK_EMPTY, "white", "black")
243 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
244 stc.STC_MARK_EMPTY, "white", "black")
245 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
246 stc.STC_MARK_EMPTY, "white", "black")
227 247
228 248 elif self.fold_symbols == 2:
229 249 # Like a flattened tree control using circular headers and curved joins
230 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_CIRCLEMINUS, "white", "#404040")
231 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_CIRCLEPLUS, "white", "#404040")
232 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#404040")
233 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNERCURVE, "white", "#404040")
234 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040")
235 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
236 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE, "white", "#404040")
250 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
251 stc.STC_MARK_CIRCLEMINUS, "white", "#404040")
252 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
253 stc.STC_MARK_CIRCLEPLUS, "white", "#404040")
254 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
255 stc.STC_MARK_VLINE, "white", "#404040")
256 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
257 stc.STC_MARK_LCORNERCURVE, "white", "#404040")
258 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
259 stc.STC_MARK_CIRCLEPLUSCONNECTED, "white", "#404040")
260 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
261 stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
262 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
263 stc.STC_MARK_TCORNERCURVE, "white", "#404040")
237 264
238 265 elif self.fold_symbols == 3:
239 266 # Like a flattened tree control using square headers
240 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080")
241 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080")
242 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080")
243 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080")
244 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
245 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
246 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080")
267 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, \
268 stc.STC_MARK_BOXMINUS, "white", "#808080")
269 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, \
270 stc.STC_MARK_BOXPLUS, "white", "#808080")
271 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, \
272 stc.STC_MARK_VLINE, "white", "#808080")
273 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, \
274 stc.STC_MARK_LCORNER, "white", "#808080")
275 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, \
276 stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
277 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, \
278 stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
279 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, \
280 stc.STC_MARK_TCORNER, "white", "#808080")
247 281
248 282
249 283 self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
@@ -363,7 +397,7 b' class PythonSTC(stc.StyledTextCtrl):'
363 397 if braceAtCaret < 0:
364 398 charAfter = self.GetCharAt(caretPos)
365 399 styleAfter = self.GetStyleAt(caretPos)
366
400
367 401 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
368 402 braceAtCaret = caretPos
369 403
This diff has been collapsed as it changes many lines, (1829 lines changed) Show them Hide them
@@ -1,885 +1,944 b''
1 #!/usr/bin/python
2 # -*- coding: iso-8859-15 -*-
3 '''
4 Provides IPython WX console widgets.
5
6 @author: Laurent Dufrechou
7 laurent.dufrechou _at_ gmail.com
8 This WX widget is based on the original work of Eitan Isaacson
9 that provided the console for the GTK toolkit.
10
11 Original work from:
12 @author: Eitan Isaacson
13 @organization: IBM Corporation
14 @copyright: Copyright (c) 2007 IBM Corporation
15 @license: BSD
16
17 All rights reserved. This program and the accompanying materials are made
18 available under the terms of the BSD which accompanies this distribution, and
19 is available at U{http://www.opensource.org/licenses/bsd-license.php}
20 '''
21
22 __version__ = 0.8
23 __author__ = "Laurent Dufrechou"
24 __email__ = "laurent.dufrechou _at_ gmail.com"
25 __license__ = "BSD"
26
27 import wx
28 import wx.stc as stc
29
30 import re
31 from StringIO import StringIO
32
33 import sys
34 import codecs
35 import locale
36 for enc in (locale.getpreferredencoding(),
37 sys.getfilesystemencoding(),
38 sys.getdefaultencoding()):
39 try:
40 codecs.lookup(enc)
41 ENCODING = enc
42 break
43 except LookupError:
44 pass
45 else:
46 ENCODING = 'utf-8'
47
48 from ipshell_nonblocking import NonBlockingIPShell
49
50 class WxNonBlockingIPShell(NonBlockingIPShell):
51 '''
52 An NonBlockingIPShell Thread that is WX dependent.
53 '''
54 def __init__(self, parent,
55 argv=[],user_ns={},user_global_ns=None,
56 cin=None, cout=None, cerr=None,
57 ask_exit_handler=None):
58
59 NonBlockingIPShell.__init__(self, argv, user_ns, user_global_ns,
60 cin, cout, cerr,
61 ask_exit_handler)
62
63 self.parent = parent
64
65 self.ask_exit_callback = ask_exit_handler
66 self._IP.exit = self._askExit
67
68 def addGUIShortcut(self, text, func):
69 wx.CallAfter(self.parent.add_button_handler,
70 button_info={ 'text':text,
71 'func':self.parent.doExecuteLine(func)})
72
73 def _askExit(self):
74 wx.CallAfter(self.ask_exit_callback, ())
75
76 def _afterExecute(self):
77 wx.CallAfter(self.parent.evtStateExecuteDone, ())
78
79
80 class WxConsoleView(stc.StyledTextCtrl):
81 '''
82 Specialized styled text control view for console-like workflow.
83 We use here a scintilla frontend thus it can be reused in any GUI that
84 supports scintilla with less work.
85
86 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.
87 (with Black background)
88 @type ANSI_COLORS_BLACK: dictionary
89
90 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.
91 (with White background)
92 @type ANSI_COLORS_WHITE: dictionary
93
94 @ivar color_pat: Regex of terminal color pattern
95 @type color_pat: _sre.SRE_Pattern
96 '''
97 ANSI_STYLES_BLACK = {'0;30': [0, 'WHITE'], '0;31': [1, 'RED'],
98 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
99 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
100 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
101 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
102 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
103 '1;34': [12, 'LIGHT BLUE'], '1;35':
104 [13, 'MEDIUM VIOLET RED'],
105 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
106
107 ANSI_STYLES_WHITE = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
108 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
109 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
110 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
111 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
112 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
113 '1;34': [12, 'LIGHT BLUE'], '1;35':
114 [13, 'MEDIUM VIOLET RED'],
115 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
116
117 def __init__(self, parent, prompt, intro="", background_color="BLACK",
118 pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
119 style=0, autocomplete_mode = 'IPYTHON'):
120 '''
121 Initialize console view.
122
123 @param parent: Parent widget
124 @param prompt: User specified prompt
125 @type intro: string
126 @param intro: User specified startup introduction string
127 @type intro: string
128 @param background_color: Can be BLACK or WHITE
129 @type background_color: string
130 @param other: init param of styledTextControl (can be used as-is)
131 @param autocomplete_mode: Can be 'IPYTHON' or 'STC'
132 'IPYTHON' show autocompletion the ipython way
133 'STC" show it scintilla text control way
134 '''
135 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
136
137 ####### Scintilla configuration ###################################
138
139 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
140 # the widget
141 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
142 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
143
144 #We draw a line at position 80
145 self.SetEdgeMode(stc.STC_EDGE_LINE)
146 self.SetEdgeColumn(80)
147 self.SetEdgeColour(wx.LIGHT_GREY)
148
149 #self.SetViewWhiteSpace(True)
150 #self.SetViewEOL(True)
151 self.SetEOLMode(stc.STC_EOL_CRLF)
152 #self.SetWrapMode(stc.STC_WRAP_CHAR)
153 #self.SetWrapMode(stc.STC_WRAP_WORD)
154 self.SetBufferedDraw(True)
155 #self.SetUseAntiAliasing(True)
156 self.SetLayoutCache(stc.STC_CACHE_PAGE)
157 self.SetUndoCollection(False)
158 self.SetUseTabs(True)
159 self.SetIndent(4)
160 self.SetTabWidth(4)
161
162 self.EnsureCaretVisible()
163
164 self.SetMargins(3, 3) #text is moved away from border with 3px
165 # Suppressing Scintilla margins
166 self.SetMarginWidth(0, 0)
167 self.SetMarginWidth(1, 0)
168 self.SetMarginWidth(2, 0)
169
170 self.background_color = background_color
171 self.buildStyles()
172
173 self.indent = 0
174 self.prompt_count = 0
175 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
176
177 self.write(intro)
178 self.setPrompt(prompt)
179 self.showPrompt()
180
181 self.autocomplete_mode = autocomplete_mode
182
183 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
184
185 def buildStyles(self):
186 #we define platform specific fonts
187 if wx.Platform == '__WXMSW__':
188 faces = { 'times': 'Times New Roman',
189 'mono' : 'Courier New',
190 'helv' : 'Arial',
191 'other': 'Comic Sans MS',
192 'size' : 10,
193 'size2': 8,
194 }
195 elif wx.Platform == '__WXMAC__':
196 faces = { 'times': 'Times New Roman',
197 'mono' : 'Monaco',
198 'helv' : 'Arial',
199 'other': 'Comic Sans MS',
200 'size' : 10,
201 'size2': 8,
202 }
203 else:
204 faces = { 'times': 'Times',
205 'mono' : 'Courier',
206 'helv' : 'Helvetica',
207 'other': 'new century schoolbook',
208 'size' : 10,
209 'size2': 8,
210 }
211
212 # make some styles
213 if self.background_color != "BLACK":
214 self.background_color = "WHITE"
215 self.SetCaretForeground("BLACK")
216 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
217 else:
218 self.SetCaretForeground("WHITE")
219 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
220
221 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
222 "fore:%s,back:%s,size:%d,face:%s"
223 % (self.ANSI_STYLES['0;30'][1],
224 self.background_color,
225 faces['size'], faces['mono']))
226 self.StyleClearAll()
227 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
228 "fore:#FF0000,back:#0000FF,bold")
229 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
230 "fore:#000000,back:#FF0000,bold")
231
232 for style in self.ANSI_STYLES.values():
233 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
234
235 #######################################################################
236
237 def setBackgroundColor(self, color):
238 self.background_color = color
239 self.buildStyles()
240
241 def getBackgroundColor(self, color):
242 return self.background_color
243
244 def asyncWrite(self, text):
245 '''
246 Write given text to buffer in an asynchroneous way.
247 It is used from another thread to be able to acces the GUI.
248 @param text: Text to append
249 @type text: string
250 '''
251 try:
252 #print >>sys.__stdout__,'entering'
253 wx.MutexGuiEnter()
254 #print >>sys.__stdout__,'locking the GUI'
255
256 #be sure not to be interrutpted before the MutexGuiLeave!
257 self.write(text)
258
259 #print >>sys.__stdout__,'done'
260
261 except KeyboardInterrupt:
262 #print >>sys.__stdout__,'got keyboard interrupt'
263 wx.MutexGuiLeave()
264 #print >>sys.__stdout__,'interrupt unlock the GUI'
265 raise KeyboardInterrupt
266 wx.MutexGuiLeave()
267 #print >>sys.__stdout__,'normal unlock the GUI'
268
269
270 def write(self, text):
271 '''
272 Write given text to buffer.
273
274 @param text: Text to append.
275 @type text: string
276 '''
277 segments = self.color_pat.split(text)
278 segment = segments.pop(0)
279 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
280 self.AppendText(segment)
281
282 if segments:
283 ansi_tags = self.color_pat.findall(text)
284
285 for tag in ansi_tags:
286 i = segments.index(tag)
287 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
288 self.AppendText(segments[i+1])
289
290 if tag != '0':
291 self.SetStyling(len(segments[i+1]), self.ANSI_STYLES[tag][0])
292
293 segments.pop(i)
294
295 self.moveCursor(self.getCurrentLineEnd())
296
297 def getPromptLen(self):
298 '''
299 Return the length of current prompt
300 '''
301 return len(str(self.prompt_count)) + 7
302
303 def setPrompt(self, prompt):
304 self.prompt = prompt
305
306 def setIndentation(self, indentation):
307 self.indent = indentation
308
309 def setPromptCount(self, count):
310 self.prompt_count = count
311
312 def showPrompt(self):
313 '''
314 Prints prompt at start of line.
315
316 @param prompt: Prompt to print.
317 @type prompt: string
318 '''
319 self.write(self.prompt)
320 #now we update the position of end of prompt
321 self.current_start = self.getCurrentLineEnd()
322
323 autoindent = self.indent*' '
324 autoindent = autoindent.replace(' ','\t')
325 self.write(autoindent)
326
327 def changeLine(self, text):
328 '''
329 Replace currently entered command line with given text.
330
331 @param text: Text to use as replacement.
332 @type text: string
333 '''
334 self.SetSelection(self.getCurrentPromptStart(), self.getCurrentLineEnd())
335 self.ReplaceSelection(text)
336 self.moveCursor(self.getCurrentLineEnd())
337
338 def getCurrentPromptStart(self):
339 return self.current_start
340
341 def getCurrentLineStart(self):
342 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
343
344 def getCurrentLineEnd(self):
345 return self.GetLength()
346
347 def getCurrentLine(self):
348 '''
349 Get text in current command line.
350
351 @return: Text of current command line.
352 @rtype: string
353 '''
354 return self.GetTextRange(self.getCurrentPromptStart(),
355 self.getCurrentLineEnd())
356
357 def moveCursorOnNewValidKey(self):
358 #If cursor is at wrong position put it at last line...
359 if self.GetCurrentPos() < self.getCurrentPromptStart():
360 self.GotoPos(self.getCurrentPromptStart())
361
362 def removeFromTo(self, from_pos, to_pos):
363 if from_pos < to_pos:
364 self.SetSelection(from_pos, to_pos)
365 self.DeleteBack()
366
367 def removeCurrentLine(self):
368 self.LineDelete()
369
370 def moveCursor(self, position):
371 self.GotoPos(position)
372
373 def getCursorPos(self):
374 return self.GetCurrentPos()
375
376 def selectFromTo(self, from_pos, to_pos):
377 self.SetSelectionStart(from_pos)
378 self.SetSelectionEnd(to_pos)
379
380 def writeHistory(self, history):
381 self.removeFromTo(self.getCurrentPromptStart(), self.getCurrentLineEnd())
382 self.changeLine(history)
383
384 def setCompletionMethod(self, completion):
385 if completion in ['IPYTHON', 'STC']:
386 self.autocomplete_mode = completion
387 else:
388 raise AttributeError
389
390 def getCompletionMethod(self, completion):
391 return self.autocomplete_mode
392
393 def writeCompletion(self, possibilities):
394 if self.autocomplete_mode == 'IPYTHON':
395 max_len = len(max(possibilities, key=len))
396 max_symbol = ' '*max_len
397
398 #now we check how much symbol we can put on a line...
399 test_buffer = max_symbol + ' '*4
400
401 allowed_symbols = 80/len(test_buffer)
402 if allowed_symbols == 0:
403 allowed_symbols = 1
404
405 pos = 1
406 buf = ''
407 for symbol in possibilities:
408 #buf += symbol+'\n'#*spaces)
409 if pos < allowed_symbols:
410 spaces = max_len - len(symbol) + 4
411 buf += symbol+' '*spaces
412 pos += 1
413 else:
414 buf += symbol+'\n'
415 pos = 1
416 self.write(buf)
417 else:
418 possibilities.sort() # Python sorts are case sensitive
419 self.AutoCompSetIgnoreCase(False)
420 self.AutoCompSetAutoHide(False)
421 #let compute the length ot last word
422 splitter = [' ', '(', '[', '{']
423 last_word = self.getCurrentLine()
424 for breaker in splitter:
425 last_word = last_word.split(breaker)[-1]
426 self.AutoCompShow(len(last_word), " ".join(possibilities))
427
428 def _onKeypress(self, event, skip=True):
429 '''
430 Key press callback used for correcting behavior for console-like
431 interfaces. For example 'home' should go to prompt, not to begining of
432 line.
433
434 @param widget: Widget that key press accored in.
435 @type widget: gtk.Widget
436 @param event: Event object
437 @type event: gtk.gdk.Event
438
439 @return: Return True if event as been catched.
440 @rtype: boolean
441 '''
442
443 if not self.AutoCompActive():
444 if event.GetKeyCode() == wx.WXK_HOME:
445 if event.Modifiers == wx.MOD_NONE:
446 self.moveCursorOnNewValidKey()
447 self.moveCursor(self.getCurrentPromptStart())
448 return True
449 elif event.Modifiers == wx.MOD_SHIFT:
450 self.moveCursorOnNewValidKey()
451 self.selectFromTo(self.getCurrentPromptStart(), self.getCursorPos())
452 return True
453 else:
454 return False
455
456 elif event.GetKeyCode() == wx.WXK_LEFT:
457 if event.Modifiers == wx.MOD_NONE:
458 self.moveCursorOnNewValidKey()
459
460 self.moveCursor(self.getCursorPos()-1)
461 if self.getCursorPos() < self.getCurrentPromptStart():
462 self.moveCursor(self.getCurrentPromptStart())
463 return True
464
465 elif event.GetKeyCode() == wx.WXK_BACK:
466 self.moveCursorOnNewValidKey()
467 if self.getCursorPos() > self.getCurrentPromptStart():
468 event.Skip()
469 return True
470
471 if skip:
472 if event.GetKeyCode() not in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]\
473 and event.Modifiers == wx.MOD_NONE:
474 self.moveCursorOnNewValidKey()
475
476 event.Skip()
477 return True
478 return False
479 else:
480 event.Skip()
481
482 def OnUpdateUI(self, evt):
483 # check for matching braces
484 braceAtCaret = -1
485 braceOpposite = -1
486 charBefore = None
487 caretPos = self.GetCurrentPos()
488
489 if caretPos > 0:
490 charBefore = self.GetCharAt(caretPos - 1)
491 styleBefore = self.GetStyleAt(caretPos - 1)
492
493 # check before
494 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
495 braceAtCaret = caretPos - 1
496
497 # check after
498 if braceAtCaret < 0:
499 charAfter = self.GetCharAt(caretPos)
500 styleAfter = self.GetStyleAt(caretPos)
501
502 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
503 braceAtCaret = caretPos
504
505 if braceAtCaret >= 0:
506 braceOpposite = self.BraceMatch(braceAtCaret)
507
508 if braceAtCaret != -1 and braceOpposite == -1:
509 self.BraceBadLight(braceAtCaret)
510 else:
511 self.BraceHighlight(braceAtCaret, braceOpposite)
512 #pt = self.PointFromPosition(braceOpposite)
513 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
514 #print pt
515 #self.Refresh(False)
516
517 class IPShellWidget(wx.Panel):
518 '''
519 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
520 If you want to port this to any other GUI toolkit, just replace the
521 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
522 from whatever container you want. I've choosed to derivate from a wx.Panel
523 because it seems to be more useful
524 Any idea to make it more 'generic' welcomed.
525 '''
526
527 def __init__(self, parent, intro=None,
528 background_color="BLACK", add_button_handler=None,
529 wx_ip_shell=None, user_ns={},user_global_ns=None,
530 ):
531 '''
532 Initialize.
533 Instanciate an IPython thread.
534 Instanciate a WxConsoleView.
535 Redirect I/O to console.
536 '''
537 wx.Panel.__init__(self,parent,wx.ID_ANY)
538
539 self.parent = parent
540 ### IPython non blocking shell instanciation ###
541 self.cout = StringIO()
542 self.add_button_handler = add_button_handler
543
544 if wx_ip_shell is not None:
545 self.IP = wx_ip_shell
546 else:
547 self.IP = WxNonBlockingIPShell(self,
548 cout = self.cout, cerr = self.cout,
549 ask_exit_handler = self.askExitCallback)
550
551 ### IPython wx console view instanciation ###
552 #If user didn't defined an intro text, we create one for him
553 #If you really wnat an empty intro just call wxIPythonViewPanel
554 #with intro=''
555 if intro is None:
556 welcome_text = "Welcome to WxIPython Shell.\n\n"
557 welcome_text+= self.IP.getBanner()
558 welcome_text+= "!command -> Execute command in shell\n"
559 welcome_text+= "TAB -> Autocompletion\n"
560 else:
561 welcome_text = intro
562
563 self.text_ctrl = WxConsoleView(self,
564 self.IP.getPrompt(),
565 intro=welcome_text,
566 background_color=background_color)
567
568 self.cout.write = self.text_ctrl.asyncWrite
569
570 option_text = wx.StaticText(self, -1, "Options:")
571 self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion")
572 #self.completion_option.SetValue(False)
573 self.background_option = wx.CheckBox(self, -1, "White Background")
574 #self.background_option.SetValue(False)
575
576 self.options={'completion':{'value':'IPYTHON',
577 'checkbox':self.completion_option,'STC':True,'IPYTHON':False,
578 'setfunc':self.text_ctrl.setCompletionMethod},
579 'background_color':{'value':'BLACK',
580 'checkbox':self.background_option,'WHITE':True,'BLACK':False,
581 'setfunc':self.text_ctrl.setBackgroundColor},
582 }
583 self.reloadOptions(self.options)
584
585 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
586 self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion)
587 self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor)
588
589 ### making the layout of the panel ###
590 sizer = wx.BoxSizer(wx.VERTICAL)
591 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
592 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
593 sizer.Add(option_sizer, 0)
594 option_sizer.AddMany([(10, 20),
595 (option_text, 0, wx.ALIGN_CENTER_VERTICAL),
596 (5, 5),
597 (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL),
598 (8, 8),
599 (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL)
600 ])
601 self.SetAutoLayout(True)
602 sizer.Fit(self)
603 sizer.SetSizeHints(self)
604 self.SetSizer(sizer)
605 #and we focus on the widget :)
606 self.SetFocus()
607
608 #widget state management (for key handling different cases)
609 self.setCurrentState('IDLE')
610 self.pager_state = 'DONE'
611 self.raw_input_current_line = 0
612
613 def askExitCallback(self, event):
614 self.askExitHandler(event)
615
616 #---------------------- IPython Thread Management ------------------------
617 def stateDoExecuteLine(self):
618 lines=self.text_ctrl.getCurrentLine()
619 self.text_ctrl.write('\n')
620 lines_to_execute = lines.replace('\t',' '*4)
621 lines_to_execute = lines_to_execute.replace('\r','')
622 self.IP.doExecute(lines_to_execute.encode(ENCODING))
623 self.updateHistoryTracker(lines)
624 self.setCurrentState('WAIT_END_OF_EXECUTION')
625
626 def evtStateExecuteDone(self,evt):
627 self.doc = self.IP.getDocText()
628 self.help = self.IP.getHelpText()
629 if self.doc:
630 self.pager_lines = self.doc[7:].split('\n')
631 self.pager_state = 'INIT'
632 self.setCurrentState('SHOW_DOC')
633 self.pager(self.doc)
634 elif self.help:
635 self.pager_lines = self.help.split('\n')
636 self.pager_state = 'INIT'
637 self.setCurrentState('SHOW_DOC')
638 self.pager(self.help)
639 else:
640 self.stateShowPrompt()
641
642 def stateShowPrompt(self):
643 self.setCurrentState('SHOW_PROMPT')
644 self.text_ctrl.setPrompt(self.IP.getPrompt())
645 self.text_ctrl.setIndentation(self.IP.getIndentation())
646 self.text_ctrl.setPromptCount(self.IP.getPromptCount())
647 self.text_ctrl.showPrompt()
648 self.IP.initHistoryIndex()
649 self.setCurrentState('IDLE')
650
651 def setCurrentState(self, state):
652 self.cur_state = state
653 self.updateStatusTracker(self.cur_state)
654
655 def pager(self,text):
656
657 if self.pager_state == 'INIT':
658 #print >>sys.__stdout__,"PAGER state:",self.pager_state
659 self.pager_nb_lines = len(self.pager_lines)
660 self.pager_index = 0
661 self.pager_do_remove = False
662 self.text_ctrl.write('\n')
663 self.pager_state = 'PROCESS_LINES'
664
665 if self.pager_state == 'PROCESS_LINES':
666 #print >>sys.__stdout__,"PAGER state:",self.pager_state
667 if self.pager_do_remove == True:
668 self.text_ctrl.removeCurrentLine()
669 self.pager_do_remove = False
670
671 if self.pager_nb_lines > 10:
672 #print >>sys.__stdout__,"PAGER processing 10 lines"
673 if self.pager_index > 0:
674 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
675 else:
676 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
677
678 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
679 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
680 self.pager_index += 10
681 self.pager_nb_lines -= 10
682 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
683 self.pager_do_remove = True
684 self.pager_state = 'WAITING'
685 return
686 else:
687 #print >>sys.__stdout__,"PAGER processing last lines"
688 if self.pager_nb_lines > 0:
689 if self.pager_index > 0:
690 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
691 else:
692 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
693
694 self.pager_index += 1
695 self.pager_nb_lines -= 1
696 if self.pager_nb_lines > 0:
697 for line in self.pager_lines[self.pager_index:]:
698 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
699 self.pager_nb_lines = 0
700 self.pager_state = 'DONE'
701 self.stateShowPrompt()
702
703 #------------------------ Key Handler ------------------------------------
704 def keyPress(self, event):
705 '''
706 Key press callback with plenty of shell goodness, like history,
707 autocompletions, etc.
708 '''
709 if event.GetKeyCode() == ord('C'):
710 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
711 if self.cur_state == 'WAIT_END_OF_EXECUTION':
712 #we raise an exception inside the IPython thread container
713 self.IP.ce.raise_exc(KeyboardInterrupt)
714 return
715
716 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
717 #mode if AutoComp has been set as inactive
718 if self.cur_state == 'COMPLETING':
719 if not self.text_ctrl.AutoCompActive():
720 self.cur_state = 'IDLE'
721 else:
722 event.Skip()
723
724 if event.KeyCode == wx.WXK_RETURN:
725 if self.cur_state == 'IDLE':
726 #we change the state ot the state machine
727 self.setCurrentState('DO_EXECUTE_LINE')
728 self.stateDoExecuteLine()
729 return
730
731 if self.pager_state == 'WAITING':
732 self.pager_state = 'PROCESS_LINES'
733 self.pager(self.doc)
734 return
735
736 if self.cur_state == 'WAITING_USER_INPUT':
737 line=self.text_ctrl.getCurrentLine()
738 self.text_ctrl.write('\n')
739 self.setCurrentState('WAIT_END_OF_EXECUTION')
740 return
741
742 if event.GetKeyCode() in [ord('q'),ord('Q')]:
743 if self.pager_state == 'WAITING':
744 self.pager_state = 'DONE'
745 self.text_ctrl.write('\n')
746 self.stateShowPrompt()
747 return
748
749 if self.cur_state == 'WAITING_USER_INPUT':
750 event.Skip()
751
752 if self.cur_state == 'IDLE':
753 if event.KeyCode == wx.WXK_UP:
754 history = self.IP.historyBack()
755 self.text_ctrl.writeHistory(history)
756 return
757 if event.KeyCode == wx.WXK_DOWN:
758 history = self.IP.historyForward()
759 self.text_ctrl.writeHistory(history)
760 return
761 if event.KeyCode == wx.WXK_TAB:
762 #if line empty we disable tab completion
763 if not self.text_ctrl.getCurrentLine().strip():
764 self.text_ctrl.write('\t')
765 return
766 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
767 if len(possibilities) > 1:
768 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
769 cur_slice = self.text_ctrl.getCurrentLine()
770 self.text_ctrl.write('\n')
771 self.text_ctrl.writeCompletion(possibilities)
772 self.text_ctrl.write('\n')
773
774 self.text_ctrl.showPrompt()
775 self.text_ctrl.write(cur_slice)
776 self.text_ctrl.changeLine(completed or cur_slice)
777 else:
778 self.cur_state = 'COMPLETING'
779 self.text_ctrl.writeCompletion(possibilities)
780 else:
781 self.text_ctrl.changeLine(completed or cur_slice)
782 return
783 event.Skip()
784
785 #------------------------ Option Section ---------------------------------
786 def evtCheckOptionCompletion(self, event):
787 if event.IsChecked():
788 self.options['completion']['value']='STC'
789 else:
790 self.options['completion']['value']='IPYTHON'
791 self.text_ctrl.setCompletionMethod(self.options['completion']['value'])
792 self.updateOptionTracker('completion',
793 self.options['completion']['value'])
794 self.text_ctrl.SetFocus()
795
796 def evtCheckOptionBackgroundColor(self, event):
797 if event.IsChecked():
798 self.options['background_color']['value']='WHITE'
799 else:
800 self.options['background_color']['value']='BLACK'
801 self.text_ctrl.setBackgroundColor(self.options['background_color']['value'])
802 self.updateOptionTracker('background_color',
803 self.options['background_color']['value'])
804 self.text_ctrl.SetFocus()
805
806 def getOptions(self):
807 return self.options
808
809 def reloadOptions(self,options):
810 self.options = options
811 for key in self.options.keys():
812 value = self.options[key]['value']
813 self.options[key]['checkbox'].SetValue(self.options[key][value])
814 self.options[key]['setfunc'](value)
815
816
817 #------------------------ Hook Section -----------------------------------
818 def updateOptionTracker(self,name,value):
819 '''
820 Default history tracker (does nothing)
821 '''
822 pass
823
824 def setOptionTrackerHook(self,func):
825 '''
826 Define a new history tracker
827 '''
828 self.updateOptionTracker = func
829
830 def updateHistoryTracker(self,command_line):
831 '''
832 Default history tracker (does nothing)
833 '''
834 pass
835
836 def setHistoryTrackerHook(self,func):
837 '''
838 Define a new history tracker
839 '''
840 self.updateHistoryTracker = func
841
842 def updateStatusTracker(self,status):
843 '''
844 Default status tracker (does nothing)
845 '''
846 pass
847
848 def setStatusTrackerHook(self,func):
849 '''
850 Define a new status tracker
851 '''
852 self.updateStatusTracker = func
853
854 def askExitHandler(self, event):
855 '''
856 Default exit handler
857 '''
858 self.text_ctrl.write('\nExit callback has not been set.')
859
860 def setAskExitHandler(self, func):
861 '''
862 Define an exit handler
863 '''
864 self.askExitHandler = func
865
866 if __name__ == '__main__':
867 # Some simple code to test the shell widget.
868 class MainWindow(wx.Frame):
869 def __init__(self, parent, id, title):
870 wx.Frame.__init__(self, parent, id, title, size=(300,250))
871 self._sizer = wx.BoxSizer(wx.VERTICAL)
872 self.shell = IPShellWidget(self)
873 self._sizer.Add(self.shell, 1, wx.EXPAND)
874 self.SetSizer(self._sizer)
875 self.SetAutoLayout(1)
876 self.Show(True)
877
878 app = wx.PySimpleApp()
879 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
880 frame.SetSize((780, 460))
881 shell = frame.shell
882
883 app.MainLoop()
884
885
1 #!/usr/bin/python
2 # -*- coding: iso-8859-15 -*-
3 '''
4 Provides IPython WX console widgets.
5
6 @author: Laurent Dufrechou
7 laurent.dufrechou _at_ gmail.com
8 This WX widget is based on the original work of Eitan Isaacson
9 that provided the console for the GTK toolkit.
10
11 Original work from:
12 @author: Eitan Isaacson
13 @organization: IBM Corporation
14 @copyright: Copyright (c) 2007 IBM Corporation
15 @license: BSD
16
17 All rights reserved. This program and the accompanying materials are made
18 available under the terms of the BSD which accompanies this distribution, and
19 is available at U{http://www.opensource.org/licenses/bsd-license.php}
20 '''
21
22 __version__ = 0.9
23 __author__ = "Laurent Dufrechou"
24 __email__ = "laurent.dufrechou _at_ gmail.com"
25 __license__ = "BSD"
26
27 import wx
28 import wx.stc as stc
29
30 import re
31 from StringIO import StringIO
32
33 import sys
34 import codecs
35 import locale
36 import time
37
38 for enc in (locale.getpreferredencoding(),
39 sys.getfilesystemencoding(),
40 sys.getdefaultencoding()):
41 try:
42 codecs.lookup(enc)
43 ENCODING = enc
44 break
45 except LookupError:
46 pass
47 else:
48 ENCODING = 'utf-8'
49
50 from ipshell_nonblocking import NonBlockingIPShell
51
52 class WxNonBlockingIPShell(NonBlockingIPShell):
53 '''
54 An NonBlockingIPShell Thread that is WX dependent.
55 '''
56 def __init__(self, parent,
57 argv=[],user_ns={},user_global_ns=None,
58 cin=None, cout=None, cerr=None,
59 ask_exit_handler=None):
60
61 NonBlockingIPShell.__init__(self, argv, user_ns, user_global_ns,
62 cin, cout, cerr,
63 ask_exit_handler)
64
65 self.parent = parent
66
67 self.ask_exit_callback = ask_exit_handler
68 self._IP.exit = self._ask_exit
69
70 def addGUIShortcut(self, text, func):
71 wx.CallAfter(self.parent.add_button_handler,
72 button_info={ 'text':text,
73 'func':self.parent.doExecuteLine(func)})
74
75 def _raw_input(self, prompt=''):
76 """ A replacement from python's raw_input.
77 """
78 self.answer = None
79 if(self._threading == True):
80 wx.CallAfter(self._yesNoBox, prompt)
81 while self.answer is None:
82 time.sleep(.1)
83 else:
84 self._yesNoBox(prompt)
85 return self.answer
86
87 def _yesNoBox(self, prompt):
88 """ yes/no box managed with wx.CallAfter jsut in case caler is executed in a thread"""
89 dlg = wx.TextEntryDialog(
90 self.parent, prompt,
91 'Input requested', 'Python')
92 dlg.SetValue("")
93
94 answer = ''
95 if dlg.ShowModal() == wx.ID_OK:
96 answer = dlg.GetValue()
97
98 dlg.Destroy()
99 self.answer = answer
100
101 def _ask_exit(self):
102 wx.CallAfter(self.ask_exit_callback, ())
103
104 def _after_execute(self):
105 wx.CallAfter(self.parent.evtStateExecuteDone, ())
106
107
108 class WxConsoleView(stc.StyledTextCtrl):
109 '''
110 Specialized styled text control view for console-like workflow.
111 We use here a scintilla frontend thus it can be reused in any GUI that
112 supports scintilla with less work.
113
114 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.
115 (with Black background)
116 @type ANSI_COLORS_BLACK: dictionary
117
118 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.
119 (with White background)
120 @type ANSI_COLORS_WHITE: dictionary
121
122 @ivar color_pat: Regex of terminal color pattern
123 @type color_pat: _sre.SRE_Pattern
124 '''
125 ANSI_STYLES_BLACK = {'0;30': [0, 'WHITE'], '0;31': [1, 'RED'],
126 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
127 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
128 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
129 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
130 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
131 '1;34': [12, 'LIGHT BLUE'], '1;35':
132 [13, 'MEDIUM VIOLET RED'],
133 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
134
135 ANSI_STYLES_WHITE = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
136 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
137 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
138 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
139 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
140 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
141 '1;34': [12, 'LIGHT BLUE'], '1;35':
142 [13, 'MEDIUM VIOLET RED'],
143 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
144
145 def __init__(self, parent, prompt, intro="", background_color="BLACK",
146 pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
147 style=0, autocomplete_mode = 'IPYTHON'):
148 '''
149 Initialize console view.
150
151 @param parent: Parent widget
152 @param prompt: User specified prompt
153 @type intro: string
154 @param intro: User specified startup introduction string
155 @type intro: string
156 @param background_color: Can be BLACK or WHITE
157 @type background_color: string
158 @param other: init param of styledTextControl (can be used as-is)
159 @param autocomplete_mode: Can be 'IPYTHON' or 'STC'
160 'IPYTHON' show autocompletion the ipython way
161 'STC" show it scintilla text control way
162 '''
163 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
164
165 ####### Scintilla configuration ###################################
166
167 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
168 # the widget
169 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
170 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
171
172 #We draw a line at position 80
173 self.SetEdgeMode(stc.STC_EDGE_LINE)
174 self.SetEdgeColumn(80)
175 self.SetEdgeColour(wx.LIGHT_GREY)
176
177 #self.SetViewWhiteSpace(True)
178 #self.SetViewEOL(True)
179 self.SetEOLMode(stc.STC_EOL_CRLF)
180 #self.SetWrapMode(stc.STC_WRAP_CHAR)
181 #self.SetWrapMode(stc.STC_WRAP_WORD)
182 self.SetBufferedDraw(True)
183 #self.SetUseAntiAliasing(True)
184 self.SetLayoutCache(stc.STC_CACHE_PAGE)
185 self.SetUndoCollection(False)
186 self.SetUseTabs(True)
187 self.SetIndent(4)
188 self.SetTabWidth(4)
189
190 self.EnsureCaretVisible()
191
192 self.SetMargins(3, 3) #text is moved away from border with 3px
193 # Suppressing Scintilla margins
194 self.SetMarginWidth(0, 0)
195 self.SetMarginWidth(1, 0)
196 self.SetMarginWidth(2, 0)
197
198 self.background_color = background_color
199 self.buildStyles()
200
201 self.indent = 0
202 self.prompt_count = 0
203 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
204
205 self.write(intro)
206 self.setPrompt(prompt)
207 self.showPrompt()
208
209 self.autocomplete_mode = autocomplete_mode
210
211 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
212
213 def buildStyles(self):
214 #we define platform specific fonts
215 if wx.Platform == '__WXMSW__':
216 faces = { 'times': 'Times New Roman',
217 'mono' : 'Courier New',
218 'helv' : 'Arial',
219 'other': 'Comic Sans MS',
220 'size' : 10,
221 'size2': 8,
222 }
223 elif wx.Platform == '__WXMAC__':
224 faces = { 'times': 'Times New Roman',
225 'mono' : 'Monaco',
226 'helv' : 'Arial',
227 'other': 'Comic Sans MS',
228 'size' : 10,
229 'size2': 8,
230 }
231 else:
232 faces = { 'times': 'Times',
233 'mono' : 'Courier',
234 'helv' : 'Helvetica',
235 'other': 'new century schoolbook',
236 'size' : 10,
237 'size2': 8,
238 }
239
240 # make some styles
241 if self.background_color != "BLACK":
242 self.background_color = "WHITE"
243 self.SetCaretForeground("BLACK")
244 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
245 else:
246 self.SetCaretForeground("WHITE")
247 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
248
249 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
250 "fore:%s,back:%s,size:%d,face:%s"
251 % (self.ANSI_STYLES['0;30'][1],
252 self.background_color,
253 faces['size'], faces['mono']))
254 self.StyleClearAll()
255 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
256 "fore:#FF0000,back:#0000FF,bold")
257 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
258 "fore:#000000,back:#FF0000,bold")
259
260 for style in self.ANSI_STYLES.values():
261 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
262
263 #######################################################################
264
265 def setBackgroundColor(self, color):
266 self.background_color = color
267 self.buildStyles()
268
269 def getBackgroundColor(self, color):
270 return self.background_color
271
272 def asyncWrite(self, text):
273 '''
274 Write given text to buffer in an asynchroneous way.
275 It is used from another thread to be able to acces the GUI.
276 @param text: Text to append
277 @type text: string
278 '''
279 try:
280 wx.MutexGuiEnter()
281
282 #be sure not to be interrutpted before the MutexGuiLeave!
283 self.write(text)
284
285 except KeyboardInterrupt:
286 wx.MutexGuiLeave()
287 raise KeyboardInterrupt
288 wx.MutexGuiLeave()
289
290
291 def write(self, text):
292 '''
293 Write given text to buffer.
294
295 @param text: Text to append.
296 @type text: string
297 '''
298 segments = self.color_pat.split(text)
299 segment = segments.pop(0)
300 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
301 self.AppendText(segment)
302
303 if segments:
304 ansi_tags = self.color_pat.findall(text)
305
306 for tag in ansi_tags:
307 i = segments.index(tag)
308 self.StartStyling(self.getCurrentLineEnd(), 0xFF)
309 self.AppendText(segments[i+1])
310
311 if tag != '0':
312 self.SetStyling(len(segments[i+1]), self.ANSI_STYLES[tag][0])
313
314 segments.pop(i)
315
316 self.moveCursor(self.getCurrentLineEnd())
317
318 def getPromptLen(self):
319 '''
320 Return the length of current prompt
321 '''
322 return len(str(self.prompt_count)) + 7
323
324 def setPrompt(self, prompt):
325 self.prompt = prompt
326
327 def setIndentation(self, indentation):
328 self.indent = indentation
329
330 def setPromptCount(self, count):
331 self.prompt_count = count
332
333 def showPrompt(self):
334 '''
335 Prints prompt at start of line.
336
337 @param prompt: Prompt to print.
338 @type prompt: string
339 '''
340 self.write(self.prompt)
341 #now we update the position of end of prompt
342 self.current_start = self.getCurrentLineEnd()
343
344 autoindent = self.indent*' '
345 autoindent = autoindent.replace(' ','\t')
346 self.write(autoindent)
347
348 def changeLine(self, text):
349 '''
350 Replace currently entered command line with given text.
351
352 @param text: Text to use as replacement.
353 @type text: string
354 '''
355 self.SetSelection(self.getCurrentPromptStart(), self.getCurrentLineEnd())
356 self.ReplaceSelection(text)
357 self.moveCursor(self.getCurrentLineEnd())
358
359 def getCurrentPromptStart(self):
360 return self.current_start
361
362 def getCurrentLineStart(self):
363 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
364
365 def getCurrentLineEnd(self):
366 return self.GetLength()
367
368 def getCurrentLine(self):
369 '''
370 Get text in current command line.
371
372 @return: Text of current command line.
373 @rtype: string
374 '''
375 return self.GetTextRange(self.getCurrentPromptStart(),
376 self.getCurrentLineEnd())
377
378 def moveCursorOnNewValidKey(self):
379 #If cursor is at wrong position put it at last line...
380 if self.GetCurrentPos() < self.getCurrentPromptStart():
381 self.GotoPos(self.getCurrentPromptStart())
382
383 def removeFromTo(self, from_pos, to_pos):
384 if from_pos < to_pos:
385 self.SetSelection(from_pos, to_pos)
386 self.DeleteBack()
387
388 def removeCurrentLine(self):
389 self.LineDelete()
390
391 def moveCursor(self, position):
392 self.GotoPos(position)
393
394 def getCursorPos(self):
395 return self.GetCurrentPos()
396
397 def selectFromTo(self, from_pos, to_pos):
398 self.SetSelectionStart(from_pos)
399 self.SetSelectionEnd(to_pos)
400
401 def writeHistory(self, history):
402 self.removeFromTo(self.getCurrentPromptStart(), self.getCurrentLineEnd())
403 self.changeLine(history)
404
405 def setCompletionMethod(self, completion):
406 if completion in ['IPYTHON', 'STC']:
407 self.autocomplete_mode = completion
408 else:
409 raise AttributeError
410
411 def getCompletionMethod(self, completion):
412 return self.autocomplete_mode
413
414 def writeCompletion(self, possibilities):
415 if self.autocomplete_mode == 'IPYTHON':
416 max_len = len(max(possibilities, key=len))
417 max_symbol = ' '*max_len
418
419 #now we check how much symbol we can put on a line...
420 test_buffer = max_symbol + ' '*4
421
422 allowed_symbols = 80/len(test_buffer)
423 if allowed_symbols == 0:
424 allowed_symbols = 1
425
426 pos = 1
427 buf = ''
428 for symbol in possibilities:
429 #buf += symbol+'\n'#*spaces)
430 if pos < allowed_symbols:
431 spaces = max_len - len(symbol) + 4
432 buf += symbol+' '*spaces
433 pos += 1
434 else:
435 buf += symbol+'\n'
436 pos = 1
437 self.write(buf)
438 else:
439 possibilities.sort() # Python sorts are case sensitive
440 self.AutoCompSetIgnoreCase(False)
441 self.AutoCompSetAutoHide(False)
442 #let compute the length ot last word
443 splitter = [' ', '(', '[', '{','=']
444 last_word = self.getCurrentLine()
445 for breaker in splitter:
446 last_word = last_word.split(breaker)[-1]
447 self.AutoCompShow(len(last_word), " ".join(possibilities))
448
449 def _onKeypress(self, event, skip=True):
450 '''
451 Key press callback used for correcting behavior for console-like
452 interfaces. For example 'home' should go to prompt, not to begining of
453 line.
454
455 @param widget: Widget that key press accored in.
456 @type widget: gtk.Widget
457 @param event: Event object
458 @type event: gtk.gdk.Event
459
460 @return: Return True if event as been catched.
461 @rtype: boolean
462 '''
463 if not self.AutoCompActive():
464 if event.GetKeyCode() == wx.WXK_HOME:
465 if event.Modifiers == wx.MOD_NONE:
466 self.moveCursorOnNewValidKey()
467 self.moveCursor(self.getCurrentPromptStart())
468 return True
469 elif event.Modifiers == wx.MOD_SHIFT:
470 self.moveCursorOnNewValidKey()
471 self.selectFromTo(self.getCurrentPromptStart(), self.getCursorPos())
472 return True
473 else:
474 return False
475
476 elif event.GetKeyCode() == wx.WXK_LEFT:
477 if event.Modifiers == wx.MOD_NONE:
478 self.moveCursorOnNewValidKey()
479
480 self.moveCursor(self.getCursorPos()-1)
481 if self.getCursorPos() < self.getCurrentPromptStart():
482 self.moveCursor(self.getCurrentPromptStart())
483 return True
484
485 elif event.GetKeyCode() == wx.WXK_BACK:
486 self.moveCursorOnNewValidKey()
487 if self.getCursorPos() > self.getCurrentPromptStart():
488 event.Skip()
489 return True
490
491 if skip:
492 if event.GetKeyCode() not in [wx.WXK_PAGEUP, wx.WXK_PAGEDOWN]\
493 and event.Modifiers == wx.MOD_NONE:
494 self.moveCursorOnNewValidKey()
495
496 event.Skip()
497 return True
498 return False
499 else:
500 event.Skip()
501
502 def OnUpdateUI(self, evt):
503 # check for matching braces
504 braceAtCaret = -1
505 braceOpposite = -1
506 charBefore = None
507 caretPos = self.GetCurrentPos()
508
509 if caretPos > 0:
510 charBefore = self.GetCharAt(caretPos - 1)
511 styleBefore = self.GetStyleAt(caretPos - 1)
512
513 # check before
514 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
515 braceAtCaret = caretPos - 1
516
517 # check after
518 if braceAtCaret < 0:
519 charAfter = self.GetCharAt(caretPos)
520 styleAfter = self.GetStyleAt(caretPos)
521
522 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
523 braceAtCaret = caretPos
524
525 if braceAtCaret >= 0:
526 braceOpposite = self.BraceMatch(braceAtCaret)
527
528 if braceAtCaret != -1 and braceOpposite == -1:
529 self.BraceBadLight(braceAtCaret)
530 else:
531 self.BraceHighlight(braceAtCaret, braceOpposite)
532 #pt = self.PointFromPosition(braceOpposite)
533 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
534 #print pt
535 #self.Refresh(False)
536
537 class IPShellWidget(wx.Panel):
538 '''
539 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
540 If you want to port this to any other GUI toolkit, just replace the
541 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
542 from whatever container you want. I've choosed to derivate from a wx.Panel
543 because it seems to be more useful
544 Any idea to make it more 'generic' welcomed.
545 '''
546
547 def __init__(self, parent, intro=None,
548 background_color="BLACK", add_button_handler=None,
549 wx_ip_shell=None, user_ns={},user_global_ns=None,
550 ):
551 '''
552 Initialize.
553 Instanciate an IPython thread.
554 Instanciate a WxConsoleView.
555 Redirect I/O to console.
556 '''
557 wx.Panel.__init__(self,parent,wx.ID_ANY)
558
559 self.parent = parent
560 ### IPython non blocking shell instanciation ###
561 self.cout = StringIO()
562 self.add_button_handler = add_button_handler
563
564 if wx_ip_shell is not None:
565 self.IP = wx_ip_shell
566 else:
567 self.IP = WxNonBlockingIPShell(self,
568 cout = self.cout, cerr = self.cout,
569 ask_exit_handler = self.askExitCallback)
570
571 ### IPython wx console view instanciation ###
572 #If user didn't defined an intro text, we create one for him
573 #If you really wnat an empty intro just call wxIPythonViewPanel
574 #with intro=''
575 if intro is None:
576 welcome_text = "Welcome to WxIPython Shell.\n\n"
577 welcome_text+= self.IP.get_banner()
578 welcome_text+= "!command -> Execute command in shell\n"
579 welcome_text+= "TAB -> Autocompletion\n"
580 else:
581 welcome_text = intro
582
583 self.text_ctrl = WxConsoleView(self,
584 self.IP.get_prompt(),
585 intro=welcome_text,
586 background_color=background_color)
587
588 option_text = wx.StaticText(self, -1, "Options:")
589 self.completion_option = wx.CheckBox(self, -1, "Scintilla Completion")
590 self.completion_option.SetToolTip(wx.ToolTip(
591 "Selects the completion type:\nEither Ipython default style or Scintilla one"))
592 #self.completion_option.SetValue(False)
593 self.background_option = wx.CheckBox(self, -1, "White Background")
594 self.background_option.SetToolTip(wx.ToolTip(
595 "Selects the back ground color: BLACK or WHITE"))
596 #self.background_option.SetValue(False)
597 self.threading_option = wx.CheckBox(self, -1, "Execute in thread")
598 self.threading_option.SetToolTip(wx.ToolTip(
599 "Use threading: infinite loop don't freeze the GUI and commands can be breaked\nNo threading: maximum compatibility"))
600 #self.threading_option.SetValue(False)
601
602 self.options={'completion':{'value':'IPYTHON',
603 'checkbox':self.completion_option,'STC':True,'IPYTHON':False,
604 'setfunc':self.text_ctrl.setCompletionMethod},
605 'background_color':{'value':'BLACK',
606 'checkbox':self.background_option,'WHITE':True,'BLACK':False,
607 'setfunc':self.text_ctrl.setBackgroundColor},
608 'threading':{'value':'True',
609 'checkbox':self.threading_option,'True':True,'False':False,
610 'setfunc':self.IP.set_threading},
611 }
612
613 #self.cout.write dEfault option is asynchroneous because default sate is threading ON
614 self.cout.write = self.text_ctrl.asyncWrite
615 #we reloard options
616 self.reloadOptions(self.options)
617
618 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
619 self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion)
620 self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor)
621 self.threading_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionThreading)
622
623 ### making the layout of the panel ###
624 sizer = wx.BoxSizer(wx.VERTICAL)
625 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
626 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
627 sizer.Add(option_sizer, 0)
628 option_sizer.AddMany([(10, 20),
629 (option_text, 0, wx.ALIGN_CENTER_VERTICAL),
630 (5, 5),
631 (self.completion_option, 0, wx.ALIGN_CENTER_VERTICAL),
632 (8, 8),
633 (self.background_option, 0, wx.ALIGN_CENTER_VERTICAL),
634 (8, 8),
635 (self.threading_option, 0, wx.ALIGN_CENTER_VERTICAL)
636 ])
637 self.SetAutoLayout(True)
638 sizer.Fit(self)
639 sizer.SetSizeHints(self)
640 self.SetSizer(sizer)
641 #and we focus on the widget :)
642 self.SetFocus()
643
644 #widget state management (for key handling different cases)
645 self.setCurrentState('IDLE')
646 self.pager_state = 'DONE'
647 self.raw_input_current_line = 0
648
649 def askExitCallback(self, event):
650 self.askExitHandler(event)
651
652 #---------------------- IPython Thread Management ------------------------
653 def stateDoExecuteLine(self):
654 lines=self.text_ctrl.getCurrentLine()
655 self.text_ctrl.write('\n')
656 lines_to_execute = lines.replace('\t',' '*4)
657 lines_to_execute = lines_to_execute.replace('\r','')
658 self.IP.do_execute(lines_to_execute.encode(ENCODING))
659 self.updateHistoryTracker(lines)
660 if(self.text_ctrl.getCursorPos()!=0):
661 self.text_ctrl.removeCurrentLine()
662 self.setCurrentState('WAIT_END_OF_EXECUTION')
663
664 def evtStateExecuteDone(self,evt):
665 self.doc = self.IP.get_doc_text()
666 self.help = self.IP.get_help_text()
667 if self.doc:
668 self.pager_lines = self.doc[7:].split('\n')
669 self.pager_state = 'INIT'
670 self.setCurrentState('SHOW_DOC')
671 self.pager(self.doc)
672 elif self.help:
673 self.pager_lines = self.help.split('\n')
674 self.pager_state = 'INIT'
675 self.setCurrentState('SHOW_DOC')
676 self.pager(self.help)
677 else:
678 if(self.text_ctrl.getCursorPos()!=0):
679 self.text_ctrl.removeCurrentLine()
680 self.stateShowPrompt()
681
682 def stateShowPrompt(self):
683 self.setCurrentState('SHOW_PROMPT')
684 self.text_ctrl.setPrompt(self.IP.get_prompt())
685 self.text_ctrl.setIndentation(self.IP.get_indentation())
686 self.text_ctrl.setPromptCount(self.IP.get_prompt_count())
687 self.text_ctrl.showPrompt()
688 self.IP.init_history_index()
689 self.setCurrentState('IDLE')
690
691 def setCurrentState(self, state):
692 self.cur_state = state
693 self.updateStatusTracker(self.cur_state)
694
695 def pager(self,text):
696
697 if self.pager_state == 'INIT':
698 #print >>sys.__stdout__,"PAGER state:",self.pager_state
699 self.pager_nb_lines = len(self.pager_lines)
700 self.pager_index = 0
701 self.pager_do_remove = False
702 self.text_ctrl.write('\n')
703 self.pager_state = 'PROCESS_LINES'
704
705 if self.pager_state == 'PROCESS_LINES':
706 #print >>sys.__stdout__,"PAGER state:",self.pager_state
707 if self.pager_do_remove == True:
708 self.text_ctrl.removeCurrentLine()
709 self.pager_do_remove = False
710
711 if self.pager_nb_lines > 10:
712 #print >>sys.__stdout__,"PAGER processing 10 lines"
713 if self.pager_index > 0:
714 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
715 else:
716 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
717
718 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
719 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
720 self.pager_index += 10
721 self.pager_nb_lines -= 10
722 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
723 self.pager_do_remove = True
724 self.pager_state = 'WAITING'
725 return
726 else:
727 #print >>sys.__stdout__,"PAGER processing last lines"
728 if self.pager_nb_lines > 0:
729 if self.pager_index > 0:
730 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
731 else:
732 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
733
734 self.pager_index += 1
735 self.pager_nb_lines -= 1
736 if self.pager_nb_lines > 0:
737 for line in self.pager_lines[self.pager_index:]:
738 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
739 self.pager_nb_lines = 0
740 self.pager_state = 'DONE'
741 self.stateShowPrompt()
742
743 #------------------------ Key Handler ------------------------------------
744 def keyPress(self, event):
745 '''
746 Key press callback with plenty of shell goodness, like history,
747 autocompletions, etc.
748 '''
749 if event.GetKeyCode() == ord('C'):
750 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
751 if self.cur_state == 'WAIT_END_OF_EXECUTION':
752 #we raise an exception inside the IPython thread container
753 self.IP.ce.raise_exc(KeyboardInterrupt)
754 return
755
756 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
757 #mode if AutoComp has been set as inactive
758 if self.cur_state == 'COMPLETING':
759 if not self.text_ctrl.AutoCompActive():
760 self.cur_state = 'IDLE'
761 else:
762 event.Skip()
763
764 if event.KeyCode == wx.WXK_RETURN:
765 if self.cur_state == 'IDLE':
766 #we change the state ot the state machine
767 self.setCurrentState('DO_EXECUTE_LINE')
768 self.stateDoExecuteLine()
769 return
770
771 if self.pager_state == 'WAITING':
772 self.pager_state = 'PROCESS_LINES'
773 self.pager(self.doc)
774 return
775
776 if self.cur_state == 'WAITING_USER_INPUT':
777 line=self.text_ctrl.getCurrentLine()
778 self.text_ctrl.write('\n')
779 self.setCurrentState('WAIT_END_OF_EXECUTION')
780 return
781
782 if event.GetKeyCode() in [ord('q'),ord('Q')]:
783 if self.pager_state == 'WAITING':
784 self.pager_state = 'DONE'
785 self.text_ctrl.write('\n')
786 self.stateShowPrompt()
787 return
788
789 if self.cur_state == 'WAITING_USER_INPUT':
790 event.Skip()
791
792 if self.cur_state == 'IDLE':
793 if event.KeyCode == wx.WXK_UP:
794 history = self.IP.history_back()
795 self.text_ctrl.writeHistory(history)
796 return
797 if event.KeyCode == wx.WXK_DOWN:
798 history = self.IP.history_forward()
799 self.text_ctrl.writeHistory(history)
800 return
801 if event.KeyCode == wx.WXK_TAB:
802 #if line empty we disable tab completion
803 if not self.text_ctrl.getCurrentLine().strip():
804 self.text_ctrl.write('\t')
805 return
806 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
807 if len(possibilities) > 1:
808 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
809 cur_slice = self.text_ctrl.getCurrentLine()
810 self.text_ctrl.write('\n')
811 self.text_ctrl.writeCompletion(possibilities)
812 self.text_ctrl.write('\n')
813
814 self.text_ctrl.showPrompt()
815 self.text_ctrl.write(cur_slice)
816 self.text_ctrl.changeLine(completed or cur_slice)
817 else:
818 self.cur_state = 'COMPLETING'
819 self.text_ctrl.writeCompletion(possibilities)
820 else:
821 self.text_ctrl.changeLine(completed or cur_slice)
822 return
823 event.Skip()
824
825 #------------------------ Option Section ---------------------------------
826 def evtCheckOptionCompletion(self, event):
827 if event.IsChecked():
828 self.options['completion']['value']='STC'
829 else:
830 self.options['completion']['value']='IPYTHON'
831 self.text_ctrl.setCompletionMethod(self.options['completion']['value'])
832 self.updateOptionTracker('completion',
833 self.options['completion']['value'])
834 self.text_ctrl.SetFocus()
835
836 def evtCheckOptionBackgroundColor(self, event):
837 if event.IsChecked():
838 self.options['background_color']['value']='WHITE'
839 else:
840 self.options['background_color']['value']='BLACK'
841 self.text_ctrl.setBackgroundColor(self.options['background_color']['value'])
842 self.updateOptionTracker('background_color',
843 self.options['background_color']['value'])
844 self.text_ctrl.SetFocus()
845
846 def evtCheckOptionThreading(self, event):
847 if event.IsChecked():
848 self.options['threading']['value']='True'
849 self.IP.set_threading(True)
850 self.cout.write = self.text_ctrl.asyncWrite
851 else:
852 self.options['threading']['value']='False'
853 self.IP.set_threading(False)
854 self.cout.write = self.text_ctrl.write
855 self.updateOptionTracker('threading',
856 self.options['threading']['value'])
857 self.text_ctrl.SetFocus()
858
859 def getOptions(self):
860 return self.options
861
862 def reloadOptions(self,options):
863 self.options = options
864 for key in self.options.keys():
865 value = self.options[key]['value']
866 self.options[key]['checkbox'].SetValue(self.options[key][value])
867 self.options[key]['setfunc'](value)
868
869 if self.options['threading']['value']=='True':
870 self.IP.set_threading(True)
871 self.cout.write = self.text_ctrl.asyncWrite
872 else:
873 self.IP.set_threading(False)
874 self.cout.write = self.text_ctrl.write
875
876 #------------------------ Hook Section -----------------------------------
877 def updateOptionTracker(self,name,value):
878 '''
879 Default history tracker (does nothing)
880 '''
881 pass
882
883 def setOptionTrackerHook(self,func):
884 '''
885 Define a new history tracker
886 '''
887 self.updateOptionTracker = func
888
889 def updateHistoryTracker(self,command_line):
890 '''
891 Default history tracker (does nothing)
892 '''
893 pass
894
895 def setHistoryTrackerHook(self,func):
896 '''
897 Define a new history tracker
898 '''
899 self.updateHistoryTracker = func
900
901 def updateStatusTracker(self,status):
902 '''
903 Default status tracker (does nothing)
904 '''
905 pass
906
907 def setStatusTrackerHook(self,func):
908 '''
909 Define a new status tracker
910 '''
911 self.updateStatusTracker = func
912
913 def askExitHandler(self, event):
914 '''
915 Default exit handler
916 '''
917 self.text_ctrl.write('\nExit callback has not been set.')
918
919 def setAskExitHandler(self, func):
920 '''
921 Define an exit handler
922 '''
923 self.askExitHandler = func
924
925 if __name__ == '__main__':
926 # Some simple code to test the shell widget.
927 class MainWindow(wx.Frame):
928 def __init__(self, parent, id, title):
929 wx.Frame.__init__(self, parent, id, title, size=(300,250))
930 self._sizer = wx.BoxSizer(wx.VERTICAL)
931 self.shell = IPShellWidget(self)
932 self._sizer.Add(self.shell, 1, wx.EXPAND)
933 self.SetSizer(self._sizer)
934 self.SetAutoLayout(1)
935 self.Show(True)
936
937 app = wx.PySimpleApp()
938 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
939 frame.SetSize((780, 460))
940 shell = frame.shell
941
942 app.MainLoop()
943
944
@@ -10,10 +10,18 b' from wx.lib.wordwrap import wordwrap'
10 10 from IPython.gui.wx.ipython_view import IPShellWidget
11 11 from IPython.gui.wx.ipython_history import IPythonHistoryPanel
12 12
13 #used to invoke ipython1 wx implementation
14 ### FIXME ### temporary disabled due to interference with 'show_in_pager' hook
15 is_sync_frontend_ok = False
16 try:
17 from IPython.frontend.wx.ipythonx import IPythonXController
18 except ImportError:
19 is_sync_frontend_ok = False
20
13 21 #used to create options.conf file in user directory
14 22 from IPython.ipapi import get
15 23
16 __version__ = 0.8
24 __version__ = 0.91
17 25 __author__ = "Laurent Dufrechou"
18 26 __email__ = "laurent.dufrechou _at_ gmail.com"
19 27 __license__ = "BSD"
@@ -27,7 +35,7 b' class MyFrame(wx.Frame):'
27 35 application with movables windows"""
28 36 def __init__(self, parent=None, id=-1, title="WxIPython",
29 37 pos=wx.DefaultPosition,
30 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
38 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE, sync_ok=False):
31 39 wx.Frame.__init__(self, parent, id, title, pos, size, style)
32 40 self._mgr = wx.aui.AuiManager()
33 41
@@ -41,12 +49,18 b' class MyFrame(wx.Frame):'
41 49
42 50 self.ipython_panel = IPShellWidget(self,background_color = "BLACK")
43 51 #self.ipython_panel = IPShellWidget(self,background_color = "WHITE")
44
52 if(sync_ok):
53 self.ipython_panel2 = IPythonXController(self)
54 else:
55 self.ipython_panel2 = None
45 56 self.ipython_panel.setHistoryTrackerHook(self.history_panel.write)
46 57 self.ipython_panel.setStatusTrackerHook(self.updateStatus)
47 58 self.ipython_panel.setAskExitHandler(self.OnExitDlg)
48 59 self.ipython_panel.setOptionTrackerHook(self.optionSave)
49 60
61 #Create a notebook to display different IPython shell implementations
62 self.nb = wx.aui.AuiNotebook(self)
63
50 64 self.optionLoad()
51 65
52 66 self.statusbar = self.createStatus()
@@ -55,7 +69,11 b' class MyFrame(wx.Frame):'
55 69 ########################################################################
56 70 ### add the panes to the manager
57 71 # main panels
58 self._mgr.AddPane(self.ipython_panel , wx.CENTER, "IPython Shell")
72 self._mgr.AddPane(self.nb , wx.CENTER, "IPython Shells")
73 self.nb.AddPage(self.ipython_panel , "IPython0 Shell")
74 if(sync_ok):
75 self.nb.AddPage(self.ipython_panel2, "IPython1 Synchroneous Shell")
76
59 77 self._mgr.AddPane(self.history_panel , wx.RIGHT, "IPython history")
60 78
61 79 # now we specify some panel characteristics
@@ -77,7 +95,10 b' class MyFrame(wx.Frame):'
77 95 warn_text = 'Hello from IPython and wxPython.\n'
78 96 warn_text +='Please Note that this work is still EXPERIMENTAL\n'
79 97 warn_text +='It does NOT emulate currently all the IPython functions.\n'
80
98 warn_text +="\nIf you use MATPLOTLIB with show() you'll need to deactivate the THREADING option.\n"
99 if(not sync_ok):
100 warn_text +="\n->No twisted package detected, IPython1 example deactivated."
101
81 102 dlg = wx.MessageDialog(self,
82 103 warn_text,
83 104 'Warning Box',
@@ -146,13 +167,6 b' class MyFrame(wx.Frame):'
146 167 about_menu = wx.Menu()
147 168 about_menu.Append(wx.ID_HIGHEST+3, "About")
148 169
149 #view_menu.AppendSeparator()
150 #options_menu = wx.Menu()
151 #options_menu.AppendCheckItem(wx.ID_HIGHEST+7, "Allow Floating")
152 #options_menu.AppendCheckItem(wx.ID_HIGHEST+8, "Transparent Hint")
153 #options_menu.AppendCheckItem(wx.ID_HIGHEST+9, "Transparent Hint Fade-in")
154
155
156 170 mb.Append(file_menu, "File")
157 171 mb.Append(view_menu, "View")
158 172 mb.Append(about_menu, "About")
@@ -233,17 +247,17 b' class MyFrame(wx.Frame):'
233 247 #-----------------------------------------
234 248 class MyApp(wx.PySimpleApp):
235 249 """Creating our application"""
236 def __init__(self):
250 def __init__(self, sync_ok=False):
237 251 wx.PySimpleApp.__init__(self)
238 252
239 self.frame = MyFrame()
253 self.frame = MyFrame(sync_ok=sync_ok)
240 254 self.frame.Show()
241 255
242 256 #-----------------------------------------
243 257 #Main loop
244 258 #-----------------------------------------
245 259 def main():
246 app = MyApp()
260 app = MyApp(is_sync_frontend_ok)
247 261 app.SetTopWindow(app.frame)
248 262 app.MainLoop()
249 263
General Comments 0
You need to be logged in to leave comments. Login now