##// END OF EJS Templates
Merging Laurent's WX branch, reviewed by Gael....
Fernando Perez -
r1862:321357e1 merge
parent child Browse files
Show More
@@ -1,471 +1,525
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3 '''
4 4 Provides IPython remote instance.
5 5
6 6 @author: Laurent Dufrechou
7 7 laurent.dufrechou _at_ gmail.com
8 8 @license: BSD
9 9
10 10 All rights reserved. This program and the accompanying materials are made
11 11 available under the terms of the BSD which accompanies this distribution, and
12 12 is available at U{http://www.opensource.org/licenses/bsd-license.php}
13 13 '''
14 14
15 15 __version__ = 0.9
16 16 __author__ = "Laurent Dufrechou"
17 17 __email__ = "laurent.dufrechou _at_ gmail.com"
18 18 __license__ = "BSD"
19 19
20 20 import re
21 21 import sys
22 22 import os
23 23 import locale
24 24 from thread_ex import ThreadEx
25 25
26 26 try:
27 27 import IPython
28 28 except Exception,e:
29 29 print "Error importing IPython (%s)" % str(e)
30 30 raise Exception, e
31 31
32 32 ##############################################################################
33 33 class _Helper(object):
34 34 """Redefine the built-in 'help'.
35 35 This is a wrapper around pydoc.help (with a twist).
36 36 """
37 37
38 38 def __init__(self, pager):
39 39 self._pager = pager
40 40
41 41 def __repr__(self):
42 42 return "Type help() for interactive help, " \
43 43 "or help(object) for help about object."
44 44
45 45 def __call__(self, *args, **kwds):
46 46 class DummyWriter(object):
47 47 '''Dumy class to handle help output'''
48 48 def __init__(self, pager):
49 49 self._pager = pager
50 50
51 51 def write(self, data):
52 52 '''hook to fill self._pager'''
53 53 self._pager(data)
54 54
55 55 import pydoc
56 56 pydoc.help.output = DummyWriter(self._pager)
57 57 pydoc.help.interact = lambda :1
58 58
59 59 return pydoc.help(*args, **kwds)
60 60
61 61
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:
73 72 self.instance._doc_text = None
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
81 80
82 81
83 82 ##############################################################################
84 83 class NonBlockingIPShell(object):
85 84 '''
86 85 Create an IPython instance, running the commands in a separate,
87 86 non-blocking thread.
88 87 This allows embedding in any GUI without blockage.
89 88
90 89 Note: The ThreadEx class supports asynchroneous function call
91 90 via raise_exc()
92 91 '''
93 92
94 93 def __init__(self, argv=[], user_ns={}, user_global_ns=None,
95 94 cin=None, cout=None, cerr=None,
96 95 ask_exit_handler=None):
97 96 '''
98 97 @param argv: Command line options for IPython
99 98 @type argv: list
100 99 @param user_ns: User namespace.
101 100 @type user_ns: dictionary
102 101 @param user_global_ns: User global namespace.
103 102 @type user_global_ns: dictionary.
104 103 @param cin: Console standard input.
105 104 @type cin: IO stream
106 105 @param cout: Console standard output.
107 106 @type cout: IO stream
108 107 @param cerr: Console standard error.
109 108 @type cerr: IO stream
110 109 @param exit_handler: Replacement for builtin exit() function
111 110 @type exit_handler: function
112 111 @param time_loop: Define the sleep time between two thread's loop
113 112 @type int
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
122 120 #vars used by _execute
123 121 self._iter_more = 0
124 122 self._history_level = 0
125 123 self._complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
126 124 self._prompt = str(self._IP.outputcache.prompt1).strip()
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
160 157 self._IP = IPython.Shell.make_IPython(
161 158 argv,user_ns=user_ns,
162 159 user_global_ns=user_global_ns,
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
170 168 loc = locale.getpreferredencoding()
171 169 if loc:
172 170 self._IP.stdin_encoding = loc
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
184 183 self._IP.user_ns['help'] = _Helper(self._pager_help)
185 184
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
212 253 @return: The std output string.
213 254 @rtype: string
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
221 262 @return: The std output string.
222 263 @rtype: string
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
230 271 @return: The banner string.
231 272 @rtype: string
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
239 280
240 281 @return: The prompt number
241 282 @rtype: int
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 ...:)
249 290
250 291 @return: The current prompt.
251 292 @rtype: string
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.
259 300
260 301 @return: The indentation level.
261 302 @rtype: int
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
269 310 @param ns_dict: A dictionary of symbol-values.
270 311 @type ns_dict: dictionary
271 312 '''
272 313 self._IP.user_ns.update(ns_dict)
273 314
274 315 def complete(self, line):
275 316 '''
276 317 Returns an auto completed line and/or posibilities for completion.
277 318
278 319 @param line: Given line so far.
279 320 @type line: string
280 321
281 322 @return: Line completed as for as possible,
282 323 and possible further completions.
283 324 @rtype: tuple
284 325 '''
285 326 split_line = self._complete_sep.split(line)
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
293 334 @param str1: First string.
294 335 @type str1: string
295 336 @param str2: Second string
296 337 @type str2: string
297 338
298 339 @return: Common prefix to both strings.
299 340 @rtype: string
300 341 '''
301 342 for i in range(len(str1)):
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
315 356 @return: The command string.
316 357 @rtype: string
317 358 '''
318 359 history = ''
319 360 #the below while loop is used to suppress empty history lines
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
330 371 @return: The command string.
331 372 @rtype: string
332 373 '''
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
371 412 @return: history length
372 413 @rtype: int
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
380 421 @return: Historic command stri
381 422 @rtype: string
382 423 '''
383 424 rv = self._IP.input_hist_raw[self._history_level].strip('\n')
384 425 return rv
385 426
386 427 def _pager_help(self, text):
387 428 '''
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
395 436 else:
396 437 self._help_text += text
397 438
398 439 def _pager(self, IP, text):
399 440 '''
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
411 452 @param prompt: Prompt to print. Here for compatability as replacement.
412 453 @type prompt: string
413 454
414 455 @return: The current command line text.
415 456 @rtype: string
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:
429 478 self._IP.readline_startup_hook(None)
430 479
431 480 except KeyboardInterrupt:
432 481 self._IP.write('\nKeyboardInterrupt\n')
433 482 self._IP.resetbuffer()
434 483 # keep cache in sync with the prompt counter:
435 484 self._IP.outputcache.prompt_count -= 1
436 485
437 486 if self._IP.autoindent:
438 487 self._IP.indent_current_nsp = 0
439 488 self._iter_more = 0
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()
448 499 if self._IP.autoindent:
449 500 self._IP.readline_startup_hook(self._IP.pre_readline)
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.
458 511
459 512 @param ip: Ipython instance, same as self._IP
460 513 @type cmd: Ipython instance
461 514 @param cmd: Shell command to execute.
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...
469 523 print "\x01\x1b[1;36m\x02"+result
470 524 stdout.close()
471 525 stdin.close()
@@ -1,475 +1,509
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3 import wx
4 4 import wx.stc as stc
5 5 import keyword
6 6
7 7 #-----------------------------------------
8 8 # History widget for IPython
9 9 __version__ = 0.5
10 10 __author__ = "Laurent Dufrechou"
11 11 __email__ = "laurent.dufrechou _at_ gmail.com"
12 12 __license__ = "BSD"
13 13 #-----------------------------------------
14 14 class IPythonHistoryPanel(wx.Panel):
15 15
16 16 def __init__(self, parent,flt_empty=True,
17 17 flt_doc=True,flt_cmd=True,flt_magic=True):
18 18
19 19 wx.Panel.__init__(self,parent,-1)
20 20 #text_ctrl = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE)
21 21 text_ctrl = PythonSTC(self, -1)
22 22
23 23
24 24 st_filt = wx.StaticText(self, -1, " Filter:")
25 25
26 26 self.filter_empty = wx.CheckBox(self, -1, "Empty commands")
27 27 self.filter_doc = wx.CheckBox(self, -1, "?: Doc commands")
28 28 self.filter_cmd = wx.CheckBox(self, -1, "!: Sys commands")
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
46 50 self.filter_empty.Bind(wx.EVT_CHECKBOX, self.evtCheckEmptyFilter)
47 51 self.filter_doc.Bind(wx.EVT_CHECKBOX, self.evtCheckDocFilter)
48 52 self.filter_cmd.Bind(wx.EVT_CHECKBOX, self.evtCheckCmdFilter)
49 53 self.filter_magic.Bind(wx.EVT_CHECKBOX, self.evtCheckMagicFilter)
50 54
51 55 #self.filter_empty.SetValue(flt_empty)
52 56 #self.filter_doc.SetValue(flt_doc)
53 57 #self.filter_cmd.SetValue(flt_cmd)
54 58 #self.filter_magic.SetValue(flt_magic)
55 59
56 60 sizer = wx.BoxSizer(wx.VERTICAL)
57 61
58 62 sizer.Add(text_ctrl, 1, wx.EXPAND)
59 63 sizer.AddMany( [(5,5),
60 64 st_filt,
61 65 (10,10),
62 66 self.filter_empty,
63 67 self.filter_doc,
64 68 self.filter_cmd,
65 69 self.filter_magic,
66 70 (10,10),
67 71 ])
68 72 self.SetAutoLayout(True)
69 73 sizer.Fit(self)
70 74 sizer.SetSizeHints(self)
71 75 self.SetSizer(sizer)
72 76 self.text_ctrl=text_ctrl
73 77 #text_ctrl.SetText(demoText + open('Main.py').read())
74 78 text_ctrl.EmptyUndoBuffer()
75 79 text_ctrl.Colourise(0, -1)
76 80
77 81 # line numbers in the margin
78 82 text_ctrl.SetMarginType(1, stc.STC_MARGIN_NUMBER)
79 83 text_ctrl.SetMarginWidth(1, 15)
80 84
81 85
82 86 def write(self,history_line):
83 87 add = True
84 88 if self.filter_empty.GetValue() == True and history_line == '':
85 89 add = False
86 90 if len(history_line)>0:
87 91 if self.filter_doc.GetValue() == True and history_line[-1:] == '?':
88 92 add = False
89 93 if self.filter_cmd.GetValue() == True and history_line[0] == '!':
90 94 add = False
91 95 if self.filter_magic.GetValue() == True and history_line[0] == '%':
92 96 add = False
93 97 if add:
94 98 self.text_ctrl.AppendText(history_line+'\n')
95 99
96 100 #------------------------ Option Section -----------------------------------
97 101 def processOptionCheckedEvt(self, event, name):
98 102 if event.IsChecked():
99 103 self.options[name]['value']='True'
100 104 else:
101 105 self.options[name]['value']='False'
102 106 self.updateOptionTracker(name,
103 107 self.options[name]['value'])
104 108
105 109 def evtCheckEmptyFilter(self, event):
106 110 self.processOptionCheckedEvt(event, 'filter_empty')
107 111
108 112 def evtCheckDocFilter(self, event):
109 113 self.processOptionCheckedEvt(event, 'filter_doc')
110 114
111 115 def evtCheckCmdFilter(self, event):
112 116 self.processOptionCheckedEvt(event, 'filter_cmd')
113 117
114 118 def evtCheckMagicFilter(self, event):
115 119 self.processOptionCheckedEvt(event, 'filter_magic')
116 120
117 121 def getOptions(self):
118 122 return self.options
119 123
120 124 def reloadOptions(self,options):
121 125 self.options = options
122 126 for key in self.options.keys():
123 127 value = self.options[key]['value']
124 128 self.options[key]['checkbox'].SetValue(self.options[key][value])
125 129 self.options[key]['setfunc'](value)
126 130
127 131 #------------------------ Hook Section -----------------------------------
128 132 def updateOptionTracker(self,name,value):
129 133 '''
130 134 Default history tracker (does nothing)
131 135 '''
132 136 pass
133 137
134 138 def setOptionTrackerHook(self,func):
135 139 '''
136 140 Define a new history tracker
137 141 '''
138 142 self.updateOptionTracker = func
139 143
140 144
141 145 #----------------------------------------------------------------------
142 146 # Font definition for Styled Text Control
143 147
144 148 if wx.Platform == '__WXMSW__':
145 149 faces = { 'times': 'Times New Roman',
146 150 'mono' : 'Courier New',
147 151 'helv' : 'Arial',
148 152 'other': 'Comic Sans MS',
149 153 'size' : 8,
150 154 'size2': 6,
151 155 }
152 156 elif wx.Platform == '__WXMAC__':
153 157 faces = { 'times': 'Times New Roman',
154 158 'mono' : 'Monaco',
155 159 'helv' : 'Arial',
156 160 'other': 'Comic Sans MS',
157 161 'size' : 8,
158 162 'size2': 6,
159 163 }
160 164 else:
161 165 faces = { 'times': 'Times',
162 166 'mono' : 'Courier',
163 167 'helv' : 'Helvetica',
164 168 'other': 'new century schoolbook',
165 169 'size' : 8,
166 170 'size2': 6,
167 171 }
168 172
169 173
170 174 #----------------------------------------------------------------------
171 175
172 176 class PythonSTC(stc.StyledTextCtrl):
173 177
174 178 fold_symbols = 3
175 179
176 180 def __init__(self, parent, ID,
177 181 pos=wx.DefaultPosition, size=wx.DefaultSize,
178 182 style=0):
179 183 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
180 184 #self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
181 185 #self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
182 186
183 187 self.SetLexer(stc.STC_LEX_PYTHON)
184 188 self.SetKeyWords(0, " ".join(keyword.kwlist))
185 189
186 190 #self.SetProperty("fold", "1")
187 191 #self.SetProperty("tab.timmy.whinge.level", "1")
188 192 #self.SetMargins(0,0)
189 193
190 194 #self.SetViewWhiteSpace(False)
191 195 #self.SetBufferedDraw(False)
192 196 #self.SetViewEOL(True)
193 197 self.SetEOLMode(stc.STC_EOL_CRLF)
194 198 #self.SetUseAntiAliasing(True)
195 199
196 200 self.SetEdgeMode(stc.STC_EDGE_LINE)
197 201 self.SetEdgeColumn(80)
198 202 self.SetEdgeColour(wx.LIGHT_GREY)
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)
250 284 self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
251 285 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
252 286
253 287 # Make some styles, The lexer defines what each style is used for, we
254 288 # just have to define what each style looks like. This set is adapted from
255 289 # Scintilla sample property files.
256 290
257 291 # Global default styles for all languages
258 292 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces)
259 293 self.StyleClearAll() # Reset all to be like the default
260 294
261 295 # Global default styles for all languages
262 296 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces)
263 297 self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces)
264 298 self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces)
265 299 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold")
266 300 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold")
267 301
268 302 # Python styles
269 303 # Default
270 304 self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
271 305 # Comments
272 306 self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % faces)
273 307 # Number
274 308 self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces)
275 309 # String
276 310 self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
277 311 # Single quoted string
278 312 self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
279 313 # Keyword
280 314 self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces)
281 315 # Triple quotes
282 316 self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces)
283 317 # Triple double quotes
284 318 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces)
285 319 # Class name definition
286 320 self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces)
287 321 # Function or method name definition
288 322 self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces)
289 323 # Operators
290 324 self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % faces)
291 325 # Identifiers
292 326 self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
293 327 # Comment-blocks
294 328 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces)
295 329 # End of line where string is not closed
296 330 self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % faces)
297 331
298 332 self.SetCaretForeground("BLUE")
299 333
300 334
301 335 # register some images for use in the AutoComplete box.
302 336 #self.RegisterImage(1, images.getSmilesBitmap())
303 337 #self.RegisterImage(2,
304 338 # wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16)))
305 339 #self.RegisterImage(3,
306 340 # wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16)))
307 341
308 342
309 343 def OnKeyPressed(self, event):
310 344 if self.CallTipActive():
311 345 self.CallTipCancel()
312 346 key = event.GetKeyCode()
313 347
314 348 if key == 32 and event.ControlDown():
315 349 pos = self.GetCurrentPos()
316 350
317 351 # Tips
318 352 if event.ShiftDown():
319 353 self.CallTipSetBackground("yellow")
320 354 self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n'
321 355 'show some suff, maybe parameters..\n\n'
322 356 'fubar(param1, param2)')
323 357 # Code completion
324 358 else:
325 359 #lst = []
326 360 #for x in range(50000):
327 361 # lst.append('%05d' % x)
328 362 #st = " ".join(lst)
329 363 #print len(st)
330 364 #self.AutoCompShow(0, st)
331 365
332 366 kw = keyword.kwlist[:]
333 367
334 368 kw.sort() # Python sorts are case sensitive
335 369 self.AutoCompSetIgnoreCase(False) # so this needs to match
336 370
337 371 # Images are specified with a appended "?type"
338 372 for i in range(len(kw)):
339 373 if kw[i] in keyword.kwlist:
340 374 kw[i] = kw[i]# + "?1"
341 375
342 376 self.AutoCompShow(0, " ".join(kw))
343 377 else:
344 378 event.Skip()
345 379
346 380
347 381 def OnUpdateUI(self, evt):
348 382 # check for matching braces
349 383 braceAtCaret = -1
350 384 braceOpposite = -1
351 385 charBefore = None
352 386 caretPos = self.GetCurrentPos()
353 387
354 388 if caretPos > 0:
355 389 charBefore = self.GetCharAt(caretPos - 1)
356 390 styleBefore = self.GetStyleAt(caretPos - 1)
357 391
358 392 # check before
359 393 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
360 394 braceAtCaret = caretPos - 1
361 395
362 396 # check after
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
370 404 if braceAtCaret >= 0:
371 405 braceOpposite = self.BraceMatch(braceAtCaret)
372 406
373 407 if braceAtCaret != -1 and braceOpposite == -1:
374 408 self.BraceBadLight(braceAtCaret)
375 409 else:
376 410 self.BraceHighlight(braceAtCaret, braceOpposite)
377 411 #pt = self.PointFromPosition(braceOpposite)
378 412 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
379 413 #print pt
380 414 #self.Refresh(False)
381 415
382 416
383 417 def OnMarginClick(self, evt):
384 418 # fold and unfold as needed
385 419 if evt.GetMargin() == 2:
386 420 if evt.GetShift() and evt.GetControl():
387 421 self.FoldAll()
388 422 else:
389 423 lineClicked = self.LineFromPosition(evt.GetPosition())
390 424
391 425 if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
392 426 if evt.GetShift():
393 427 self.SetFoldExpanded(lineClicked, True)
394 428 self.Expand(lineClicked, True, True, 1)
395 429 elif evt.GetControl():
396 430 if self.GetFoldExpanded(lineClicked):
397 431 self.SetFoldExpanded(lineClicked, False)
398 432 self.Expand(lineClicked, False, True, 0)
399 433 else:
400 434 self.SetFoldExpanded(lineClicked, True)
401 435 self.Expand(lineClicked, True, True, 100)
402 436 else:
403 437 self.ToggleFold(lineClicked)
404 438
405 439
406 440 def FoldAll(self):
407 441 lineCount = self.GetLineCount()
408 442 expanding = True
409 443
410 444 # find out if we are folding or unfolding
411 445 for lineNum in range(lineCount):
412 446 if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
413 447 expanding = not self.GetFoldExpanded(lineNum)
414 448 break
415 449
416 450 lineNum = 0
417 451
418 452 while lineNum < lineCount:
419 453 level = self.GetFoldLevel(lineNum)
420 454 if level & stc.STC_FOLDLEVELHEADERFLAG and \
421 455 (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
422 456
423 457 if expanding:
424 458 self.SetFoldExpanded(lineNum, True)
425 459 lineNum = self.Expand(lineNum, True)
426 460 lineNum = lineNum - 1
427 461 else:
428 462 lastChild = self.GetLastChild(lineNum, -1)
429 463 self.SetFoldExpanded(lineNum, False)
430 464
431 465 if lastChild > lineNum:
432 466 self.HideLines(lineNum+1, lastChild)
433 467
434 468 lineNum = lineNum + 1
435 469
436 470
437 471
438 472 def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
439 473 lastChild = self.GetLastChild(line, level)
440 474 line = line + 1
441 475
442 476 while line <= lastChild:
443 477 if force:
444 478 if visLevels > 0:
445 479 self.ShowLines(line, line)
446 480 else:
447 481 self.HideLines(line, line)
448 482 else:
449 483 if doExpand:
450 484 self.ShowLines(line, line)
451 485
452 486 if level == -1:
453 487 level = self.GetFoldLevel(line)
454 488
455 489 if level & stc.STC_FOLDLEVELHEADERFLAG:
456 490 if force:
457 491 if visLevels > 1:
458 492 self.SetFoldExpanded(line, True)
459 493 else:
460 494 self.SetFoldExpanded(line, False)
461 495
462 496 line = self.Expand(line, doExpand, force, visLevels-1)
463 497
464 498 else:
465 499 if doExpand and self.GetFoldExpanded(line):
466 500 line = self.Expand(line, True, force, visLevels-1)
467 501 else:
468 502 line = self.Expand(line, False, force, visLevels-1)
469 503 else:
470 504 line = line + 1
471 505
472 506 return line
473 507
474 508
475 509 #----------------------------------------------------------------------
This diff has been collapsed as it changes many lines, (1829 lines changed) Show them Hide them
@@ -1,885 +1,944
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
@@ -1,252 +1,266
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3
4 4 import wx.aui
5 5 import sys
6 6 #used for about dialog
7 7 from wx.lib.wordwrap import wordwrap
8 8
9 9 #used for ipython GUI objects
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"
20 28
21 29 #-----------------------------------------
22 30 # Creating one main frame for our
23 31 # application with movables windows
24 32 #-----------------------------------------
25 33 class MyFrame(wx.Frame):
26 34 """Creating one main frame for our
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
34 42 # notify PyAUI which frame to use
35 43 self._mgr.SetManagedWindow(self)
36 44
37 45 #create differents panels and make them persistant
38 46 self.history_panel = IPythonHistoryPanel(self)
39 47
40 48 self.history_panel.setOptionTrackerHook(self.optionSave)
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()
53 67 self.createMenu()
54 68
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
62 80 self._mgr.GetPane(self.ipython_panel).CaptionVisible(True);
63 81 self._mgr.GetPane(self.history_panel).CaptionVisible(True);
64 82 self._mgr.GetPane(self.history_panel).MinSize((200,400));
65 83
66 84 # tell the manager to "commit" all the changes just made
67 85 self._mgr.Update()
68 86
69 87 #global event handling
70 88 self.Bind(wx.EVT_CLOSE, self.OnClose)
71 89 self.Bind(wx.EVT_MENU, self.OnClose,id=wx.ID_EXIT)
72 90 self.Bind(wx.EVT_MENU, self.OnShowIPythonPanel,id=wx.ID_HIGHEST+1)
73 91 self.Bind(wx.EVT_MENU, self.OnShowHistoryPanel,id=wx.ID_HIGHEST+2)
74 92 self.Bind(wx.EVT_MENU, self.OnShowAbout, id=wx.ID_HIGHEST+3)
75 93 self.Bind(wx.EVT_MENU, self.OnShowAllPanel,id=wx.ID_HIGHEST+6)
76 94
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',
84 105 wx.OK | wx.ICON_INFORMATION
85 106 )
86 107 dlg.ShowModal()
87 108 dlg.Destroy()
88 109
89 110 def optionSave(self, name, value):
90 111 ip = get()
91 112 path = ip.IP.rc.ipythondir
92 113 opt = open(path + '/options.conf','w')
93 114
94 115 try:
95 116 options_ipython_panel = self.ipython_panel.getOptions()
96 117 options_history_panel = self.history_panel.getOptions()
97 118
98 119 for key in options_ipython_panel.keys():
99 120 opt.write(key + '=' + options_ipython_panel[key]['value']+'\n')
100 121 for key in options_history_panel.keys():
101 122 opt.write(key + '=' + options_history_panel[key]['value']+'\n')
102 123 finally:
103 124 opt.close()
104 125
105 126 def optionLoad(self):
106 127 try:
107 128 ip = get()
108 129 path = ip.IP.rc.ipythondir
109 130 opt = open(path + '/options.conf','r')
110 131 lines = opt.readlines()
111 132 opt.close()
112 133
113 134 options_ipython_panel = self.ipython_panel.getOptions()
114 135 options_history_panel = self.history_panel.getOptions()
115 136
116 137 for line in lines:
117 138 key = line.split('=')[0]
118 139 value = line.split('=')[1].replace('\n','').replace('\r','')
119 140 if key in options_ipython_panel.keys():
120 141 options_ipython_panel[key]['value'] = value
121 142 elif key in options_history_panel.keys():
122 143 options_history_panel[key]['value'] = value
123 144 else:
124 145 print >>sys.__stdout__,"Warning: key ",key,"not found in widget options. Check Options.conf"
125 146 self.ipython_panel.reloadOptions(options_ipython_panel)
126 147 self.history_panel.reloadOptions(options_history_panel)
127 148
128 149 except IOError:
129 150 print >>sys.__stdout__,"Could not open Options.conf, defaulting to default values."
130 151
131 152
132 153 def createMenu(self):
133 154 """local method used to create one menu bar"""
134 155
135 156 mb = wx.MenuBar()
136 157
137 158 file_menu = wx.Menu()
138 159 file_menu.Append(wx.ID_EXIT, "Exit")
139 160
140 161 view_menu = wx.Menu()
141 162 view_menu.Append(wx.ID_HIGHEST+1, "Show IPython Panel")
142 163 view_menu.Append(wx.ID_HIGHEST+2, "Show History Panel")
143 164 view_menu.AppendSeparator()
144 165 view_menu.Append(wx.ID_HIGHEST+6, "Show All")
145 166
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")
159 173 #mb.Append(options_menu, "Options")
160 174
161 175 self.SetMenuBar(mb)
162 176
163 177 def createStatus(self):
164 178 statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
165 179 statusbar.SetStatusWidths([-2, -3])
166 180 statusbar.SetStatusText("Ready", 0)
167 181 statusbar.SetStatusText("WxIPython "+str(__version__), 1)
168 182 return statusbar
169 183
170 184 def updateStatus(self,text):
171 185 states = {'IDLE':'Idle',
172 186 'DO_EXECUTE_LINE':'Send command',
173 187 'WAIT_END_OF_EXECUTION':'Running command',
174 188 'WAITING_USER_INPUT':'Waiting user input',
175 189 'SHOW_DOC':'Showing doc',
176 190 'SHOW_PROMPT':'Showing prompt'}
177 191 self.statusbar.SetStatusText(states[text], 0)
178 192
179 193 def OnClose(self, event):
180 194 """#event used to close program """
181 195 # deinitialize the frame manager
182 196 self._mgr.UnInit()
183 197 self.Destroy()
184 198 event.Skip()
185 199
186 200 def OnExitDlg(self, event):
187 201 dlg = wx.MessageDialog(self, 'Are you sure you want to quit WxIPython',
188 202 'WxIPython exit',
189 203 wx.ICON_QUESTION |
190 204 wx.YES_NO | wx.NO_DEFAULT
191 205 )
192 206 if dlg.ShowModal() == wx.ID_YES:
193 207 dlg.Destroy()
194 208 self._mgr.UnInit()
195 209 self.Destroy()
196 210 dlg.Destroy()
197 211
198 212 #event to display IPython pannel
199 213 def OnShowIPythonPanel(self,event):
200 214 """ #event to display Boxpannel """
201 215 self._mgr.GetPane(self.ipython_panel).Show(True)
202 216 self._mgr.Update()
203 217 #event to display History pannel
204 218 def OnShowHistoryPanel(self,event):
205 219 self._mgr.GetPane(self.history_panel).Show(True)
206 220 self._mgr.Update()
207 221
208 222 def OnShowAllPanel(self,event):
209 223 """#event to display all Pannels"""
210 224 self._mgr.GetPane(self.ipython_panel).Show(True)
211 225 self._mgr.GetPane(self.history_panel).Show(True)
212 226 self._mgr.Update()
213 227
214 228 def OnShowAbout(self, event):
215 229 # First we create and fill the info object
216 230 info = wx.AboutDialogInfo()
217 231 info.Name = "WxIPython"
218 232 info.Version = str(__version__)
219 233 info.Copyright = "(C) 2007 Laurent Dufrechou"
220 234 info.Description = wordwrap(
221 235 "A Gui that embbed a multithreaded IPython Shell",
222 236 350, wx.ClientDC(self))
223 237 info.WebSite = ("http://ipython.scipy.org/", "IPython home page")
224 238 info.Developers = [ "Laurent Dufrechou" ]
225 239 licenseText="BSD License.\nAll rights reserved. This program and the accompanying materials are made available under the terms of the BSD which accompanies this distribution, and is available at http://www.opensource.org/licenses/bsd-license.php"
226 240 info.License = wordwrap(licenseText, 500, wx.ClientDC(self))
227 241
228 242 # Then we call wx.AboutBox giving it that info object
229 243 wx.AboutBox(info)
230 244
231 245 #-----------------------------------------
232 246 #Creating our application
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
250 264 #if launched as main program run this
251 265 if __name__ == '__main__':
252 266 main()
General Comments 0
You need to be logged in to leave comments. Login now