##// END OF EJS Templates
[/gui/wx] Added a checkbox to switch background color (white/black)
ldufrechou -
Show More
@@ -1,813 +1,834 b''
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3 '''
4 4 Provides IPython WX console widgets.
5 5
6 6 @author: Laurent Dufrechou
7 7 laurent.dufrechou _at_ gmail.com
8 8 This WX widget is based on the original work of Eitan Isaacson
9 9 that provided the console for the GTK toolkit.
10 10
11 11 Original work from:
12 12 @author: Eitan Isaacson
13 13 @organization: IBM Corporation
14 14 @copyright: Copyright (c) 2007 IBM Corporation
15 15 @license: BSD
16 16
17 17 All rights reserved. This program and the accompanying materials are made
18 18 available under the terms of the BSD which accompanies this distribution, and
19 19 is available at U{http://www.opensource.org/licenses/bsd-license.php}
20 20 '''
21 21
22 22 __version__ = 0.8
23 23 __author__ = "Laurent Dufrechou"
24 24 __email__ = "laurent.dufrechou _at_ gmail.com"
25 25 __license__ = "BSD"
26 26
27 27 import wx
28 28 import wx.stc as stc
29 29 import wx.lib.newevent
30 30
31 31 import re
32 32 import sys
33 33 import locale
34 34 from StringIO import StringIO
35 35 try:
36 36 import IPython
37 37 except Exception,e:
38 38 raise "Error importing IPython (%s)" % str(e)
39 39
40 40 from ipshell_nonblocking import NonBlockingIPShell
41 41
42 42
43 43 class WxNonBlockingIPShell(NonBlockingIPShell):
44 44 '''
45 45 An NonBlockingIPShell Thread that is WX dependent.
46 46 '''
47 47 def __init__(self, parent,
48 48 argv=[],user_ns={},user_global_ns=None,
49 49 cin=None, cout=None, cerr=None,
50 50 ask_exit_handler=None):
51 51
52 52 NonBlockingIPShell.__init__(self,argv,user_ns,user_global_ns,
53 53 cin, cout, cerr,
54 54 ask_exit_handler)
55 55
56 56 self.parent = parent
57 57
58 58 self.ask_exit_callback = ask_exit_handler
59 59 self._IP.exit = self._askExit
60 60
61 61 def addGUIShortcut(self,text,func):
62 62 wx.CallAfter(self.parent.add_button_handler,
63 63 button_info={ 'text':text,
64 64 'func':self.parent.doExecuteLine(func)})
65 65
66 66 def _askExit(self):
67 67 wx.CallAfter(self.ask_exit_callback, ())
68 68
69 69 def _afterExecute(self):
70 70 wx.CallAfter(self.parent.evtStateExecuteDone, ())
71 71
72 72
73 73 class WxConsoleView(stc.StyledTextCtrl):
74 74 '''
75 75 Specialized styled text control view for console-like workflow.
76 76 We use here a scintilla frontend thus it can be reused in any GUI that
77 77 supports scintilla with less work.
78 78
79 79 @cvar ANSI_COLORS_BLACK: Mapping of terminal colors to X11 names.
80 80 (with Black background)
81 81 @type ANSI_COLORS_BLACK: dictionary
82 82
83 83 @cvar ANSI_COLORS_WHITE: Mapping of terminal colors to X11 names.
84 84 (with White background)
85 85 @type ANSI_COLORS_WHITE: dictionary
86 86
87 87 @ivar color_pat: Regex of terminal color pattern
88 88 @type color_pat: _sre.SRE_Pattern
89 89 '''
90 90 ANSI_STYLES_BLACK={'0;30': [0,'WHITE'], '0;31': [1,'RED'],
91 91 '0;32': [2,'GREEN'], '0;33': [3,'BROWN'],
92 92 '0;34': [4,'BLUE'], '0;35': [5,'PURPLE'],
93 93 '0;36': [6,'CYAN'], '0;37': [7,'LIGHT GREY'],
94 94 '1;30': [8,'DARK GREY'], '1;31': [9,'RED'],
95 95 '1;32': [10,'SEA GREEN'], '1;33': [11,'YELLOW'],
96 96 '1;34': [12,'LIGHT BLUE'], '1;35':
97 97 [13,'MEDIUM VIOLET RED'],
98 98 '1;36': [14,'LIGHT STEEL BLUE'],'1;37': [15,'YELLOW']}
99 99
100 100 ANSI_STYLES_WHITE={'0;30': [0,'BLACK'], '0;31': [1,'RED'],
101 101 '0;32': [2,'GREEN'], '0;33': [3,'BROWN'],
102 102 '0;34': [4,'BLUE'], '0;35': [5,'PURPLE'],
103 103 '0;36': [6,'CYAN'], '0;37': [7,'LIGHT GREY'],
104 104 '1;30': [8,'DARK GREY'], '1;31': [9,'RED'],
105 105 '1;32': [10,'SEA GREEN'], '1;33': [11,'YELLOW'],
106 106 '1;34': [12,'LIGHT BLUE'], '1;35':
107 107 [13,'MEDIUM VIOLET RED'],
108 108 '1;36': [14,'LIGHT STEEL BLUE'],'1;37': [15,'YELLOW']}
109 109
110 110 def __init__(self,parent,prompt,intro="",background_color="BLACK",
111 111 pos=wx.DefaultPosition, ID = -1, size=wx.DefaultSize,
112 112 style=0, autocomplete_mode = 'IPYTHON'):
113 113 '''
114 114 Initialize console view.
115 115
116 116 @param parent: Parent widget
117 117 @param prompt: User specified prompt
118 118 @type intro: string
119 119 @param intro: User specified startup introduction string
120 120 @type intro: string
121 121 @param background_color: Can be BLACK or WHITE
122 122 @type background_color: string
123 123 @param other: init param of styledTextControl (can be used as-is)
124 124 @param autocomplete_mode: Can be 'IPYTHON' or 'STC'
125 125 'IPYTHON' show autocompletion the ipython way
126 126 'STC" show it scintilla text control way
127 127 '''
128 128 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
129 129
130 130 ####### Scintilla configuration ###################################
131 131
132 132 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
133 133 # the widget
134 134 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
135 135 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
136 136
137 #We draw a line at position 80
138 self.SetEdgeMode(stc.STC_EDGE_LINE)
139 self.SetEdgeColumn(80)
140 self.SetEdgeColour(wx.LIGHT_GREY)
141
142 #self.SetViewWhiteSpace(True)
143 #self.SetViewEOL(True)
144 self.SetEOLMode(stc.STC_EOL_CRLF)
145 #self.SetWrapMode(stc.STC_WRAP_CHAR)
146 #self.SetWrapMode(stc.STC_WRAP_WORD)
147 self.SetBufferedDraw(True)
148 #self.SetUseAntiAliasing(True)
149 self.SetLayoutCache(stc.STC_CACHE_PAGE)
150
151 self.EnsureCaretVisible()
152
153 self.SetMargins(3,3) #text is moved away from border with 3px
154 # Suppressing Scintilla margins
155 self.SetMarginWidth(0,0)
156 self.SetMarginWidth(1,0)
157 self.SetMarginWidth(2,0)
158
159 self.background_color = background_color
160 self.buildStyles()
161
162 self.indent = 0
163 self.prompt_count = 0
164 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
165
166 self.write(intro)
167 self.setPrompt(prompt)
168 self.showPrompt()
169
170 self.autocomplete_mode = autocomplete_mode
171
172 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
173
174 def buildStyles(self):
137 175 #we define platform specific fonts
138 176 if wx.Platform == '__WXMSW__':
139 177 faces = { 'times': 'Times New Roman',
140 178 'mono' : 'Courier New',
141 179 'helv' : 'Arial',
142 180 'other': 'Comic Sans MS',
143 181 'size' : 10,
144 182 'size2': 8,
145 183 }
146 184 elif wx.Platform == '__WXMAC__':
147 185 faces = { 'times': 'Times New Roman',
148 186 'mono' : 'Monaco',
149 187 'helv' : 'Arial',
150 188 'other': 'Comic Sans MS',
151 189 'size' : 10,
152 190 'size2': 8,
153 191 }
154 192 else:
155 193 faces = { 'times': 'Times',
156 194 'mono' : 'Courier',
157 195 'helv' : 'Helvetica',
158 196 'other': 'new century schoolbook',
159 197 'size' : 10,
160 198 'size2': 8,
161 199 }
162 200
163 #We draw a line at position 80
164 self.SetEdgeMode(stc.STC_EDGE_LINE)
165 self.SetEdgeColumn(80)
166 self.SetEdgeColour(wx.LIGHT_GREY)
167
168 #self.SetViewWhiteSpace(True)
169 #self.SetViewEOL(True)
170 self.SetEOLMode(stc.STC_EOL_CRLF)
171 #self.SetWrapMode(stc.STC_WRAP_CHAR)
172 #self.SetWrapMode(stc.STC_WRAP_WORD)
173 self.SetBufferedDraw(True)
174 #self.SetUseAntiAliasing(True)
175 self.SetLayoutCache(stc.STC_CACHE_PAGE)
176
177 self.EnsureCaretVisible()
178
179 self.SetMargins(3,3) #text is moved away from border with 3px
180 # Suppressing Scintilla margins
181 self.SetMarginWidth(0,0)
182 self.SetMarginWidth(1,0)
183 self.SetMarginWidth(2,0)
184
185 201 # make some styles
186 if background_color != "BLACK":
202 if self.background_color != "BLACK":
187 203 self.background_color = "WHITE"
188 204 self.SetCaretForeground("BLACK")
189 205 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
190 206 else:
191 self.background_color = background_color
192 207 self.SetCaretForeground("WHITE")
193 208 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
194 209
195 210 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
196 211 "fore:%s,back:%s,size:%d,face:%s"
197 212 % (self.ANSI_STYLES['0;30'][1],
198 213 self.background_color,
199 214 faces['size'], faces['mono']))
200 215 self.StyleClearAll()
201 216 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
202 217 "fore:#FF0000,back:#0000FF,bold")
203 218 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
204 219 "fore:#000000,back:#FF0000,bold")
205
220
206 221 for style in self.ANSI_STYLES.values():
207 222 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
208 223
209 224 #######################################################################
210 225
211 self.indent = 0
212 self.prompt_count = 0
213 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
214
215 self.write(intro)
216 self.setPrompt(prompt)
217 self.showPrompt()
226 def setBackgroundColor(self,color):
227 self.background_color = color
228 self.buildStyles()
218 229
219 self.autocomplete_mode = autocomplete_mode
230 def getBackgroundColor(self,color):
231 return self.background_color
220 232
221 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
222
223 233 def asyncWrite(self, text):
224 234 '''
225 235 Write given text to buffer in an asynchroneous way.
226 236 It is used from another thread to be able to acces the GUI.
227 237 @param text: Text to append
228 238 @type text: string
229 239 '''
230 240 try:
231 241 #print >>sys.__stdout__,'entering'
232 242 wx.MutexGuiEnter()
233 243 #print >>sys.__stdout__,'locking the GUI'
234 244
235 245 #be sure not to be interrutpted before the MutexGuiLeave!
236 246 self.write(text)
237 247 #print >>sys.__stdout__,'done'
238 248
239 249 except KeyboardInterrupt:
240 250 #print >>sys.__stdout__,'got keyboard interrupt'
241 251 wx.MutexGuiLeave()
242 252 #print >>sys.__stdout__,'interrupt unlock the GUI'
243 253 raise KeyboardInterrupt
244 254 wx.MutexGuiLeave()
245 255 #print >>sys.__stdout__,'normal unlock the GUI'
246 256
247 257
248 258 def write(self, text):
249 259 '''
250 260 Write given text to buffer.
251 261
252 262 @param text: Text to append.
253 263 @type text: string
254 264 '''
255 265 segments = self.color_pat.split(text)
256 266 segment = segments.pop(0)
257 267 self.StartStyling(self.getCurrentLineEnd(),0xFF)
258 268 self.AppendText(segment)
259 269
260 270 if segments:
261 271 ansi_tags = self.color_pat.findall(text)
262 272
263 273 for tag in ansi_tags:
264 274 i = segments.index(tag)
265 275 self.StartStyling(self.getCurrentLineEnd(),0xFF)
266 276 self.AppendText(segments[i+1])
267 277
268 278 if tag != '0':
269 279 self.SetStyling(len(segments[i+1]),self.ANSI_STYLES[tag][0])
270 280
271 281 segments.pop(i)
272 282
273 283 self.moveCursor(self.getCurrentLineEnd())
274 284
275 285 def getPromptLen(self):
276 286 '''
277 287 Return the length of current prompt
278 288 '''
279 289 return len(str(self.prompt_count)) + 7
280 290
281 291 def setPrompt(self,prompt):
282 292 self.prompt = prompt
283 293
284 294 def setIndentation(self,indentation):
285 295 self.indent = indentation
286 296
287 297 def setPromptCount(self,count):
288 298 self.prompt_count = count
289 299
290 300 def showPrompt(self):
291 301 '''
292 302 Prints prompt at start of line.
293 303
294 304 @param prompt: Prompt to print.
295 305 @type prompt: string
296 306 '''
297 307 self.write(self.prompt)
298 308 #now we update the position of end of prompt
299 309 self.current_start = self.getCurrentLineEnd()
300 310
301 311 autoindent = self.indent*' '
302 312 autoindent = autoindent.replace(' ','\t')
303 313 self.write(autoindent)
304 314
305 315 def changeLine(self, text):
306 316 '''
307 317 Replace currently entered command line with given text.
308 318
309 319 @param text: Text to use as replacement.
310 320 @type text: string
311 321 '''
312 322 self.SetSelection(self.getCurrentPromptStart(),self.getCurrentLineEnd())
313 323 self.ReplaceSelection(text)
314 324 self.moveCursor(self.getCurrentLineEnd())
315 325
316 326 def getCurrentPromptStart(self):
317 327 return self.current_start
318 328
319 329 def getCurrentLineStart(self):
320 330 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
321 331
322 332 def getCurrentLineEnd(self):
323 333 return self.GetLength()
324 334
325 335 def getCurrentLine(self):
326 336 '''
327 337 Get text in current command line.
328 338
329 339 @return: Text of current command line.
330 340 @rtype: string
331 341 '''
332 342 return self.GetTextRange(self.getCurrentPromptStart(),
333 343 self.getCurrentLineEnd())
334 344
335 345 def moveCursorOnNewValidKey(self):
336 346 #If cursor is at wrong position put it at last line...
337 347 if self.GetCurrentPos() < self.getCurrentPromptStart():
338 348 self.GotoPos(self.getCurrentPromptStart())
339 349
340 350 def removeFromTo(self,from_pos,to_pos):
341 351 if from_pos < to_pos:
342 352 self.SetSelection(from_pos,to_pos)
343 353 self.DeleteBack()
344 354
345 355 def removeCurrentLine(self):
346 356 self.LineDelete()
347 357
348 358 def moveCursor(self,position):
349 359 self.GotoPos(position)
350 360
351 361 def getCursorPos(self):
352 362 return self.GetCurrentPos()
353 363
354 364 def selectFromTo(self,from_pos,to_pos):
355 365 self.SetSelectionStart(from_pos)
356 366 self.SetSelectionEnd(to_pos)
357 367
358 368 def writeHistory(self,history):
359 369 self.removeFromTo(self.getCurrentPromptStart(),self.getCurrentLineEnd())
360 370 self.changeLine(history)
361 371
362 372 def setCompletionMethod(self, completion):
363 373 if completion in ['IPYTHON','STC']:
364 374 self.autocomplete_mode = completion
365 375 else:
366 376 raise AttributeError
367 377
368 378 def getCompletionMethod(self, completion):
369 379 return self.autocomplete_mode
370 380
371 381 def writeCompletion(self, possibilities):
372 382 if self.autocomplete_mode == 'IPYTHON':
373 383 max_len = len(max(possibilities,key=len))
374 384 max_symbol =' '*max_len
375 385
376 386 #now we check how much symbol we can put on a line...
377 387 cursor_pos = self.getCursorPos()
378 388 test_buffer = max_symbol + ' '*4
379 389 current_lines = self.GetLineCount()
380 390
381 391 allowed_symbols = 80/len(test_buffer)
382 392 if allowed_symbols == 0:
383 393 allowed_symbols = 1
384 394
385 395 pos = 1
386 396 buf = ''
387 397 for symbol in possibilities:
388 398 #buf += symbol+'\n'#*spaces)
389 399 if pos<allowed_symbols:
390 400 spaces = max_len - len(symbol) + 4
391 401 buf += symbol+' '*spaces
392 402 pos += 1
393 403 else:
394 404 buf+=symbol+'\n'
395 405 pos = 1
396 406 self.write(buf)
397 407 else:
398 408 possibilities.sort() # Python sorts are case sensitive
399 409 self.AutoCompSetIgnoreCase(False)
400 410 self.AutoCompSetAutoHide(False)
401 411 #let compute the length ot last word
402 412 splitter = [' ','(','[','{']
403 413 last_word = self.getCurrentLine()
404 414 for breaker in splitter:
405 415 last_word = last_word.split(breaker)[-1]
406 416 self.AutoCompShow(len(last_word), " ".join(possibilities))
407 417
408 418 def _onKeypress(self, event, skip=True):
409 419 '''
410 420 Key press callback used for correcting behavior for console-like
411 421 interfaces. For example 'home' should go to prompt, not to begining of
412 422 line.
413 423
414 424 @param widget: Widget that key press accored in.
415 425 @type widget: gtk.Widget
416 426 @param event: Event object
417 427 @type event: gtk.gdk.Event
418 428
419 429 @return: Return True if event as been catched.
420 430 @rtype: boolean
421 431 '''
422 432
423 433 if not self.AutoCompActive():
424 434 if event.GetKeyCode() == wx.WXK_HOME:
425 435 if event.Modifiers == wx.MOD_NONE:
426 436 self.moveCursorOnNewValidKey()
427 437 self.moveCursor(self.getCurrentPromptStart())
428 438 return True
429 439 elif event.Modifiers == wx.MOD_SHIFT:
430 440 self.moveCursorOnNewValidKey()
431 441 self.selectFromTo(self.getCurrentPromptStart(),self.getCursorPos())
432 442 return True
433 443 else:
434 444 return False
435 445
436 446 elif event.GetKeyCode() == wx.WXK_LEFT:
437 447 if event.Modifiers == wx.MOD_NONE:
438 448 self.moveCursorOnNewValidKey()
439 449
440 450 self.moveCursor(self.getCursorPos()-1)
441 451 if self.getCursorPos() < self.getCurrentPromptStart():
442 452 self.moveCursor(self.getCurrentPromptStart())
443 453 return True
444 454
445 455 elif event.GetKeyCode() == wx.WXK_BACK:
446 456 self.moveCursorOnNewValidKey()
447 457 if self.getCursorPos() > self.getCurrentPromptStart():
448 458 event.Skip()
449 459 return True
450 460
451 461 if skip:
452 462 if event.GetKeyCode() not in [wx.WXK_PAGEUP,wx.WXK_PAGEDOWN] and event.Modifiers == wx.MOD_NONE:
453 463 self.moveCursorOnNewValidKey()
454 464
455 465 event.Skip()
456 466 return True
457 467 return False
458 468 else:
459 469 event.Skip()
460 470
461 471 def OnUpdateUI(self, evt):
462 472 # check for matching braces
463 473 braceAtCaret = -1
464 474 braceOpposite = -1
465 475 charBefore = None
466 476 caretPos = self.GetCurrentPos()
467 477
468 478 if caretPos > 0:
469 479 charBefore = self.GetCharAt(caretPos - 1)
470 480 styleBefore = self.GetStyleAt(caretPos - 1)
471 481
472 482 # check before
473 483 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
474 484 braceAtCaret = caretPos - 1
475 485
476 486 # check after
477 487 if braceAtCaret < 0:
478 488 charAfter = self.GetCharAt(caretPos)
479 489 styleAfter = self.GetStyleAt(caretPos)
480 490
481 491 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
482 492 braceAtCaret = caretPos
483 493
484 494 if braceAtCaret >= 0:
485 495 braceOpposite = self.BraceMatch(braceAtCaret)
486 496
487 497 if braceAtCaret != -1 and braceOpposite == -1:
488 498 self.BraceBadLight(braceAtCaret)
489 499 else:
490 500 self.BraceHighlight(braceAtCaret, braceOpposite)
491 501 #pt = self.PointFromPosition(braceOpposite)
492 502 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
493 503 #print pt
494 504 #self.Refresh(False)
495 505
496 506 class IPShellWidget(wx.Panel):
497 507 '''
498 508 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
499 509 If you want to port this to any other GUI toolkit, just replace the
500 510 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
501 511 from whatever container you want. I've choosed to derivate from a wx.Panel
502 512 because it seems to be more useful
503 513 Any idea to make it more 'generic' welcomed.
504 514 '''
505 515
506 516 def __init__(self, parent, intro=None,
507 517 background_color="BLACK", add_button_handler=None,
508 518 wx_ip_shell=None, user_ns={},user_global_ns=None,
509 519 ):
510 520 '''
511 521 Initialize.
512 522 Instanciate an IPython thread.
513 523 Instanciate a WxConsoleView.
514 524 Redirect I/O to console.
515 525 '''
516 526 wx.Panel.__init__(self,parent,wx.ID_ANY)
517 527
518 528 ### IPython non blocking shell instanciation ###
519 529 self.cout = StringIO()
520 530 self.add_button_handler = add_button_handler
521 531
522 532 if wx_ip_shell is not None:
523 533 self.IP = wx_ip_shell
524 534 else:
525 535 self.IP = WxNonBlockingIPShell(self,
526 536 cout = self.cout, cerr = self.cout,
527 537 ask_exit_handler = self.askExitCallback)
528 538
529 539 ### IPython wx console view instanciation ###
530 540 #If user didn't defined an intro text, we create one for him
531 541 #If you really wnat an empty intrp just call wxIPythonViewPanel
532 542 #with intro=''
533 543 if intro is None:
534 544 welcome_text = "Welcome to WxIPython Shell.\n\n"
535 545 welcome_text+= self.IP.getBanner()
536 546 welcome_text+= "!command -> Execute command in shell\n"
537 547 welcome_text+= "TAB -> Autocompletion\n"
538 548 else:
539 549 welcome_text = intro
540 550
541 551 self.text_ctrl = WxConsoleView(self,
542 552 self.IP.getPrompt(),
543 553 intro=welcome_text,
544 554 background_color=background_color)
545 555
546 556 self.cout.write = self.text_ctrl.asyncWrite
547 557
558 option_text = wx.StaticText(self,-1,'Options:')
548 559 self.completion_option = wx.CheckBox(self, -1, "Scintilla completion")
549 560 self.completion_option.SetValue(False)
550 option_text = wx.StaticText(self,-1,'Options:')
561 self.background_option = wx.CheckBox(self, -1, "White background")
562 self.background_option.SetValue(False)
551 563
552 564 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress)
553 self.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion, self.completion_option)
565 self.completion_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionCompletion)
566 self.background_option.Bind(wx.EVT_CHECKBOX, self.evtCheckOptionBackgroundColor)
554 567
555 568 ### making the layout of the panel ###
556 569 sizer = wx.BoxSizer(wx.VERTICAL)
557 570 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
558 571 option_sizer = wx.BoxSizer(wx.HORIZONTAL)
559 572 sizer.Add(option_sizer,0)
560 573 option_sizer.AddMany([(10,15),
561 574 option_text,
562 575 (20,15),
563 self.completion_option
576 self.completion_option,
577 self.background_option,
564 578 ])
565 579 self.SetAutoLayout(True)
566 580 sizer.Fit(self)
567 581 sizer.SetSizeHints(self)
568 582 self.SetSizer(sizer)
569 583 #and we focus on the widget :)
570 584 self.SetFocus()
571 585
572 586 #widget state management (for key handling different cases)
573 587 self.setCurrentState('IDLE')
574 588 self.pager_state = 'DONE'
575 589 self.raw_input_current_line = 0
576 590
577 591 def askExitCallback(self, event):
578 592 self.askExitHandler(event)
579 593
580 594 #---------------------- IPython Thread Management ------------------------
581 595 def stateDoExecuteLine(self):
582 596 lines=self.text_ctrl.getCurrentLine()
583 597 self.text_ctrl.write('\n')
584 598 lines_to_execute = lines.replace('\t',' '*4)
585 599 lines_to_execute = lines_to_execute.replace('\r','')
586 600 self.IP.doExecute(lines_to_execute.encode('cp1252'))
587 601 self.updateHistoryTracker(lines)
588 602 self.setCurrentState('WAIT_END_OF_EXECUTION')
589 603
590 604 def evtStateExecuteDone(self,evt):
591 605 self.doc = self.IP.getDocText()
592 606 self.help = self.IP.getHelpText()
593 607 if self.doc:
594 608 self.pager_lines = self.doc[7:].split('\n')
595 609 self.pager_state = 'INIT'
596 610 self.setCurrentState('SHOW_DOC')
597 611 self.pager(self.doc)
598 612 elif self.help:
599 613 self.pager_lines = self.help.split('\n')
600 614 self.pager_state = 'INIT'
601 615 self.setCurrentState('SHOW_DOC')
602 616 self.pager(self.help)
603 617 else:
604 618 self.stateShowPrompt()
605 619
606 620 def stateShowPrompt(self):
607 621 self.setCurrentState('SHOW_PROMPT')
608 622 self.text_ctrl.setPrompt(self.IP.getPrompt())
609 623 self.text_ctrl.setIndentation(self.IP.getIndentation())
610 624 self.text_ctrl.setPromptCount(self.IP.getPromptCount())
611 625 self.text_ctrl.showPrompt()
612 626 self.IP.initHistoryIndex()
613 627 self.setCurrentState('IDLE')
614 628
615 629 def setCurrentState(self, state):
616 630 self.cur_state = state
617 631 self.updateStatusTracker(self.cur_state)
618 632
619 633 def pager(self,text):
620 634
621 635 if self.pager_state == 'INIT':
622 636 #print >>sys.__stdout__,"PAGER state:",self.pager_state
623 637 self.pager_nb_lines = len(self.pager_lines)
624 638 self.pager_index = 0
625 639 self.pager_do_remove = False
626 640 self.text_ctrl.write('\n')
627 641 self.pager_state = 'PROCESS_LINES'
628 642
629 643 if self.pager_state == 'PROCESS_LINES':
630 644 #print >>sys.__stdout__,"PAGER state:",self.pager_state
631 645 if self.pager_do_remove == True:
632 646 self.text_ctrl.removeCurrentLine()
633 647 self.pager_do_remove = False
634 648
635 649 if self.pager_nb_lines > 10:
636 650 #print >>sys.__stdout__,"PAGER processing 10 lines"
637 651 if self.pager_index > 0:
638 652 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
639 653 else:
640 654 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
641 655
642 656 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
643 657 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
644 658 self.pager_index += 10
645 659 self.pager_nb_lines -= 10
646 660 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
647 661 self.pager_do_remove = True
648 662 self.pager_state = 'WAITING'
649 663 return
650 664 else:
651 665 #print >>sys.__stdout__,"PAGER processing last lines"
652 666 if self.pager_nb_lines > 0:
653 667 if self.pager_index > 0:
654 668 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
655 669 else:
656 670 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
657 671
658 672 self.pager_index += 1
659 673 self.pager_nb_lines -= 1
660 674 if self.pager_nb_lines > 0:
661 675 for line in self.pager_lines[self.pager_index:]:
662 676 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
663 677 self.pager_nb_lines = 0
664 678 self.pager_state = 'DONE'
665 679 self.stateShowPrompt()
666 680
667 681 #------------------------ Key Handler ------------------------------------
668 682 def keyPress(self, event):
669 683 '''
670 684 Key press callback with plenty of shell goodness, like history,
671 685 autocompletions, etc.
672 686 '''
673 687 if event.GetKeyCode() == ord('C'):
674 688 if event.Modifiers == wx.MOD_CONTROL or event.Modifiers == wx.MOD_ALT:
675 689 if self.cur_state == 'WAIT_END_OF_EXECUTION':
676 690 #we raise an exception inside the IPython thread container
677 691 self.IP.ce.raise_exc(KeyboardInterrupt)
678 692 return
679 693
680 694 #let this before 'wx.WXK_RETURN' because we have to put 'IDLE'
681 695 #mode if AutoComp has been set as inactive
682 696 if self.cur_state == 'COMPLETING':
683 697 if not self.text_ctrl.AutoCompActive():
684 698 self.cur_state = 'IDLE'
685 699 else:
686 700 event.Skip()
687 701
688 702 if event.KeyCode == wx.WXK_RETURN:
689 703 if self.cur_state == 'IDLE':
690 704 #we change the state ot the state machine
691 705 self.setCurrentState('DO_EXECUTE_LINE')
692 706 self.stateDoExecuteLine()
693 707 return
694 708
695 709 if self.pager_state == 'WAITING':
696 710 self.pager_state = 'PROCESS_LINES'
697 711 self.pager(self.doc)
698 712 return
699 713
700 714 if self.cur_state == 'WAITING_USER_INPUT':
701 715 line=self.text_ctrl.getCurrentLine()
702 716 self.text_ctrl.write('\n')
703 717 self.setCurrentState('WAIT_END_OF_EXECUTION')
704 718 return
705 719
706 720 if event.GetKeyCode() in [ord('q'),ord('Q')]:
707 721 if self.pager_state == 'WAITING':
708 722 self.pager_state = 'DONE'
709 723 self.text_ctrl.write('\n')
710 724 self.stateShowPrompt()
711 725 return
712 726
713 727 if self.cur_state == 'WAITING_USER_INPUT':
714 728 event.Skip()
715 729
716 730 if self.cur_state == 'IDLE':
717 731 if event.KeyCode == wx.WXK_UP:
718 732 history = self.IP.historyBack()
719 733 self.text_ctrl.writeHistory(history)
720 734 return
721 735 if event.KeyCode == wx.WXK_DOWN:
722 736 history = self.IP.historyForward()
723 737 self.text_ctrl.writeHistory(history)
724 738 return
725 739 if event.KeyCode == wx.WXK_TAB:
726 740 #if line empty we disable tab completion
727 741 if not self.text_ctrl.getCurrentLine().strip():
728 742 self.text_ctrl.write('\t')
729 743 return
730 744 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
731 745 if len(possibilities) > 1:
732 746 if self.text_ctrl.autocomplete_mode == 'IPYTHON':
733 747 cur_slice = self.text_ctrl.getCurrentLine()
734 748 self.text_ctrl.write('\n')
735 749 self.text_ctrl.writeCompletion(possibilities)
736 750 self.text_ctrl.write('\n')
737 751
738 752 self.text_ctrl.showPrompt()
739 753 self.text_ctrl.write(cur_slice)
740 754 self.text_ctrl.changeLine(completed or cur_slice)
741 755 else:
742 756 self.cur_state = 'COMPLETING'
743 757 self.text_ctrl.writeCompletion(possibilities)
744 758 else:
745 759 self.text_ctrl.changeLine(completed or cur_slice)
746 760 return
747 761 event.Skip()
748 762
749 763 #------------------------ Option Section ---------------------------------
750 764 def evtCheckOptionCompletion(self, event):
751 765 if event.IsChecked():
752 766 self.text_ctrl.setCompletionMethod('STC')
753 767 else:
754 768 self.text_ctrl.setCompletionMethod('IPYTHON')
755 769 self.text_ctrl.SetFocus()
756
770
771 def evtCheckOptionBackgroundColor(self, event):
772 if event.IsChecked():
773 self.text_ctrl.setBackgroundColor('WHITE')
774 else:
775 self.text_ctrl.setBackgroundColor('BLACK')
776 self.text_ctrl.SetFocus()
777
757 778 #------------------------ Hook Section -----------------------------------
758 779 def updateHistoryTracker(self,command_line):
759 780 '''
760 781 Default history tracker (does nothing)
761 782 '''
762 783 pass
763 784
764 785 def setHistoryTrackerHook(self,func):
765 786 '''
766 787 Define a new history tracker
767 788 '''
768 789 self.updateHistoryTracker = func
769 790
770 791 def updateStatusTracker(self,status):
771 792 '''
772 793 Default status tracker (does nothing)
773 794 '''
774 795 pass
775 796
776 797 def setStatusTrackerHook(self,func):
777 798 '''
778 799 Define a new status tracker
779 800 '''
780 801 self.updateStatusTracker = func
781 802
782 803 def askExitHandler(self, event):
783 804 '''
784 805 Default exit handler
785 806 '''
786 807 self.text_ctrl.write('\nExit callback has not been set.')
787 808
788 809 def setAskExitHandler(self, func):
789 810 '''
790 811 Define an exit handler
791 812 '''
792 813 self.askExitHandler = func
793 814
794 815 if __name__ == '__main__':
795 816 # Some simple code to test the shell widget.
796 817 class MainWindow(wx.Frame):
797 818 def __init__(self, parent, id, title):
798 819 wx.Frame.__init__(self, parent, id, title, size=(300,250))
799 820 self._sizer = wx.BoxSizer(wx.VERTICAL)
800 821 self.shell = IPShellWidget(self)
801 822 self._sizer.Add(self.shell, 1, wx.EXPAND)
802 823 self.SetSizer(self._sizer)
803 824 self.SetAutoLayout(1)
804 825 self.Show(True)
805 826
806 827 app = wx.PySimpleApp()
807 828 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
808 829 frame.SetSize((780, 460))
809 830 shell = frame.shell
810 831
811 832 app.MainLoop()
812 833
813 834
General Comments 0
You need to be logged in to leave comments. Login now