##// END OF EJS Templates
BIG improvement on cout management....
ldufrechou -
Show More
@@ -1,729 +1,720 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):
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 '''
125 125 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
126 126
127 127 ####### Scintilla configuration ###################################
128 128
129 129 # Ctrl + B or Ctrl + N can be used to zoomin/zoomout the text inside
130 130 # the widget
131 131 self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
132 132 self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
133 133
134 134 #we define platform specific fonts
135 135 if wx.Platform == '__WXMSW__':
136 136 faces = { 'times': 'Times New Roman',
137 137 'mono' : 'Courier New',
138 138 'helv' : 'Arial',
139 139 'other': 'Comic Sans MS',
140 140 'size' : 10,
141 141 'size2': 8,
142 142 }
143 143 elif wx.Platform == '__WXMAC__':
144 144 faces = { 'times': 'Times New Roman',
145 145 'mono' : 'Monaco',
146 146 'helv' : 'Arial',
147 147 'other': 'Comic Sans MS',
148 148 'size' : 10,
149 149 'size2': 8,
150 150 }
151 151 else:
152 152 faces = { 'times': 'Times',
153 153 'mono' : 'Courier',
154 154 'helv' : 'Helvetica',
155 155 'other': 'new century schoolbook',
156 156 'size' : 10,
157 157 'size2': 8,
158 158 }
159 159
160 160 #We draw a line at position 80
161 161 self.SetEdgeMode(stc.STC_EDGE_LINE)
162 162 self.SetEdgeColumn(80)
163 163 self.SetEdgeColour(wx.LIGHT_GREY)
164 164
165 165 #self.SetViewWhiteSpace(True)
166 166 #self.SetViewEOL(True)
167 167 self.SetEOLMode(stc.STC_EOL_CRLF)
168 168 #self.SetWrapMode(stc.STC_WRAP_CHAR)
169 169 #self.SetWrapMode(stc.STC_WRAP_WORD)
170 170 self.SetBufferedDraw(True)
171 171 #self.SetUseAntiAliasing(True)
172 172 self.SetLayoutCache(stc.STC_CACHE_PAGE)
173 173
174 174 self.EnsureCaretVisible()
175 175
176 176 self.SetMargins(3,3) #text is moved away from border with 3px
177 177 # Suppressing Scintilla margins
178 178 self.SetMarginWidth(0,0)
179 179 self.SetMarginWidth(1,0)
180 180 self.SetMarginWidth(2,0)
181 181
182 182 # make some styles
183 183 if background_color != "BLACK":
184 184 self.background_color = "WHITE"
185 185 self.SetCaretForeground("BLACK")
186 186 self.ANSI_STYLES = self.ANSI_STYLES_WHITE
187 187 else:
188 188 self.background_color = background_color
189 189 self.SetCaretForeground("WHITE")
190 190 self.ANSI_STYLES = self.ANSI_STYLES_BLACK
191 191
192 192 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
193 193 "fore:%s,back:%s,size:%d,face:%s"
194 194 % (self.ANSI_STYLES['0;30'][1],
195 195 self.background_color,
196 196 faces['size'], faces['mono']))
197 197 self.StyleClearAll()
198 198 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
199 199 "fore:#FF0000,back:#0000FF,bold")
200 200 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
201 201 "fore:#000000,back:#FF0000,bold")
202 202
203 203 for style in self.ANSI_STYLES.values():
204 204 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
205 205
206 206 #######################################################################
207 207
208 208 self.indent = 0
209 209 self.prompt_count = 0
210 210 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
211 211
212 212 self.write(intro)
213 213 self.setPrompt(prompt)
214 214 self.showPrompt()
215 215
216 216 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress, self)
217 217
218 218 def write(self, text):
219 219 '''
220 220 Write given text to buffer.
221 221
222 222 @param text: Text to append.
223 223 @type text: string
224 224 '''
225 225 segments = self.color_pat.split(text)
226 226 segment = segments.pop(0)
227 227 self.StartStyling(self.getCurrentLineEnd(),0xFF)
228 228 self.AppendText(segment)
229 229
230 230 if segments:
231 231 ansi_tags = self.color_pat.findall(text)
232 232
233 233 for tag in ansi_tags:
234 234 i = segments.index(tag)
235 235 self.StartStyling(self.getCurrentLineEnd(),0xFF)
236 236 self.AppendText(segments[i+1])
237 237
238 238 if tag != '0':
239 239 self.SetStyling(len(segments[i+1]),self.ANSI_STYLES[tag][0])
240 240
241 241 segments.pop(i)
242 242
243 243 self.moveCursor(self.getCurrentLineEnd())
244 244
245 245 def getPromptLen(self):
246 246 '''
247 247 Return the length of current prompt
248 248 '''
249 249 return len(str(self.prompt_count)) + 7
250 250
251 251 def setPrompt(self,prompt):
252 252 self.prompt = prompt
253 253
254 254 def setIndentation(self,indentation):
255 255 self.indent = indentation
256 256
257 257 def setPromptCount(self,count):
258 258 self.prompt_count = count
259 259
260 260 def showPrompt(self):
261 261 '''
262 262 Prints prompt at start of line.
263 263
264 264 @param prompt: Prompt to print.
265 265 @type prompt: string
266 266 '''
267 267 self.write(self.prompt)
268 268 #now we update the position of end of prompt
269 269 self.current_start = self.getCurrentLineEnd()
270 270
271 271 autoindent = self.indent*' '
272 272 autoindent = autoindent.replace(' ','\t')
273 273 self.write(autoindent)
274 274
275 275 def changeLine(self, text):
276 276 '''
277 277 Replace currently entered command line with given text.
278 278
279 279 @param text: Text to use as replacement.
280 280 @type text: string
281 281 '''
282 282 self.SetSelection(self.getCurrentPromptStart(),self.getCurrentLineEnd())
283 283 self.ReplaceSelection(text)
284 284 self.moveCursor(self.getCurrentLineEnd())
285 285
286 286 def getCurrentPromptStart(self):
287 287 return self.current_start
288 288
289 289 def getCurrentLineStart(self):
290 290 return self.GotoLine(self.LineFromPosition(self.GetCurrentPos()))
291 291
292 292 def getCurrentLineEnd(self):
293 293 return self.GetLength()
294 294
295 295 def getCurrentLine(self):
296 296 '''
297 297 Get text in current command line.
298 298
299 299 @return: Text of current command line.
300 300 @rtype: string
301 301 '''
302 302 return self.GetTextRange(self.getCurrentPromptStart(),
303 303 self.getCurrentLineEnd())
304 304
305 def showReturned(self, text):
306 '''
307 Show returned text from last command and print new prompt.
308
309 @param text: Text to show.
310 @type text: string
311 '''
312 self.write('\n'+text)
313 if text:
314 self.write('\n')
315 self.showPrompt()
316
317 305 def moveCursorOnNewValidKey(self):
318 306 #If cursor is at wrong position put it at last line...
319 307 if self.GetCurrentPos() < self.getCurrentPromptStart():
320 308 self.GotoPos(self.getCurrentPromptStart())
321 309
322 310 def removeFromTo(self,from_pos,to_pos):
323 311 if from_pos < to_pos:
324 312 self.SetSelection(from_pos,to_pos)
325 313 self.DeleteBack()
326 314
327 315 def removeCurrentLine(self):
328 316 self.LineDelete()
329 317
330 318 def moveCursor(self,position):
331 319 self.GotoPos(position)
332 320
333 321 def getCursorPos(self):
334 322 return self.GetCurrentPos()
335 323
336 324 def selectFromTo(self,from_pos,to_pos):
337 325 self.SetSelectionStart(from_pos)
338 326 self.SetSelectionEnd(to_pos)
339 327
340 328 def writeHistory(self,history):
341 329 self.removeFromTo(self.getCurrentPromptStart(),self.getCurrentLineEnd())
342 330 self.changeLine(history)
343 331
344 332 def writeCompletion(self, possibilities):
345 333 max_len = len(max(possibilities,key=len))
346 334 max_symbol =' '*max_len
347 335
348 336 #now we check how much symbol we can put on a line...
349 337 cursor_pos = self.getCursorPos()
350 338 test_buffer = max_symbol + ' '*4
351 339 current_lines = self.GetLineCount()
352 340
353 341 allowed_symbols = 80/len(test_buffer)
354 342 if allowed_symbols == 0:
355 343 allowed_symbols = 1
356 344
357 345 pos = 1
358 346 buf = ''
359 347 for symbol in possibilities:
360 348 #buf += symbol+'\n'#*spaces)
361 349 if pos<allowed_symbols:
362 350 spaces = max_len - len(symbol) + 4
363 351 buf += symbol+' '*spaces
364 352 pos += 1
365 353 else:
366 354 buf+=symbol+'\n'
367 355 pos = 1
368 356 self.write(buf)
369 357
370 358 def _onKeypress(self, event, skip=True):
371 359 '''
372 360 Key press callback used for correcting behavior for console-like
373 361 interfaces. For example 'home' should go to prompt, not to begining of
374 362 line.
375 363
376 364 @param widget: Widget that key press accored in.
377 365 @type widget: gtk.Widget
378 366 @param event: Event object
379 367 @type event: gtk.gdk.Event
380 368
381 369 @return: Return True if event as been catched.
382 370 @rtype: boolean
383 371 '''
384 372
385 373 if event.GetKeyCode() == wx.WXK_HOME:
386 374 if event.Modifiers == wx.MOD_NONE:
387 375 self.moveCursorOnNewValidKey()
388 376 self.moveCursor(self.getCurrentPromptStart())
389 377 return True
390 378 elif event.Modifiers == wx.MOD_SHIFT:
391 379 self.moveCursorOnNewValidKey()
392 380 self.selectFromTo(self.getCurrentPromptStart(),self.getCursorPos())
393 381 return True
394 382 else:
395 383 return False
396 384
397 385 elif event.GetKeyCode() == wx.WXK_LEFT:
398 386 if event.Modifiers == wx.MOD_NONE:
399 387 self.moveCursorOnNewValidKey()
400 388
401 389 self.moveCursor(self.getCursorPos()-1)
402 390 if self.getCursorPos() < self.getCurrentPromptStart():
403 391 self.moveCursor(self.getCurrentPromptStart())
404 392 return True
405 393
406 394 elif event.GetKeyCode() == wx.WXK_BACK:
407 395 self.moveCursorOnNewValidKey()
408 396 if self.getCursorPos() > self.getCurrentPromptStart():
409 397 self.removeFromTo(self.getCursorPos()-1,self.getCursorPos())
410 398 return True
411 399
412 400 if skip:
413 401 if event.GetKeyCode() not in [wx.WXK_PAGEUP,wx.WXK_PAGEDOWN] and event.Modifiers == wx.MOD_NONE:
414 402 self.moveCursorOnNewValidKey()
415 403
416 404 event.Skip()
417 405 return True
418 406 return False
419 407
420 408 def OnUpdateUI(self, evt):
421 409 # check for matching braces
422 410 braceAtCaret = -1
423 411 braceOpposite = -1
424 412 charBefore = None
425 413 caretPos = self.GetCurrentPos()
426 414
427 415 if caretPos > 0:
428 416 charBefore = self.GetCharAt(caretPos - 1)
429 417 styleBefore = self.GetStyleAt(caretPos - 1)
430 418
431 419 # check before
432 420 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
433 421 braceAtCaret = caretPos - 1
434 422
435 423 # check after
436 424 if braceAtCaret < 0:
437 425 charAfter = self.GetCharAt(caretPos)
438 426 styleAfter = self.GetStyleAt(caretPos)
439 427
440 428 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
441 429 braceAtCaret = caretPos
442 430
443 431 if braceAtCaret >= 0:
444 432 braceOpposite = self.BraceMatch(braceAtCaret)
445 433
446 434 if braceAtCaret != -1 and braceOpposite == -1:
447 435 self.BraceBadLight(braceAtCaret)
448 436 else:
449 437 self.BraceHighlight(braceAtCaret, braceOpposite)
450 438 #pt = self.PointFromPosition(braceOpposite)
451 439 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
452 440 #print pt
453 441 #self.Refresh(False)
454 442
455 443 class IPShellWidget(wx.Panel):
456 444 '''
457 445 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
458 446 If you want to port this to any other GUI toolkit, just replace the
459 447 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
460 448 from whatever container you want. I've choosed to derivate from a wx.Panel
461 449 because it seems to be more useful
462 450 Any idea to make it more 'generic' welcomed.
463 451 '''
452
464 453 def __init__(self, parent, intro=None,
465 454 background_color="BLACK", add_button_handler=None,
466 455 wx_ip_shell=None,
467 456 ):
468 457 '''
469 458 Initialize.
470 459 Instanciate an IPython thread.
471 460 Instanciate a WxConsoleView.
472 461 Redirect I/O to console.
473 462 '''
474 463 wx.Panel.__init__(self,parent,-1)
475 464
476 465 ### IPython non blocking shell instanciation ###
477 466 self.cout = StringIO()
478
479 467 self.add_button_handler = add_button_handler
480 468
481 469 if wx_ip_shell is not None:
482 470 self.IP = wx_ip_shell
483 471 else:
484 472 self.IP = WxNonBlockingIPShell(self,
485 473 cout = self.cout,cerr = self.cout,
486 474 ask_exit_handler = self.askExitCallback)
487 475
488 476 ### IPython wx console view instanciation ###
489 477 #If user didn't defined an intro text, we create one for him
490 478 #If you really wnat an empty intrp just call wxIPythonViewPanel
491 479 #with intro=''
492 480 if intro is None:
493 481 welcome_text = "Welcome to WxIPython Shell.\n\n"
494 482 welcome_text+= self.IP.getBanner()
495 483 welcome_text+= "!command -> Execute command in shell\n"
496 484 welcome_text+= "TAB -> Autocompletion\n"
497 485 else:
498 486 welcome_text = intro
499 487
500 488 self.text_ctrl = WxConsoleView(self,
501 489 self.IP.getPrompt(),
502 490 intro=welcome_text,
503 491 background_color=background_color)
492
493 self.cout.write = self.text_ctrl.write
504 494
505 495 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress, self.text_ctrl)
506
496
507 497 ### making the layout of the panel ###
508 498 sizer = wx.BoxSizer(wx.VERTICAL)
509 499 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
510 500 self.SetAutoLayout(True)
511 501 sizer.Fit(self)
512 502 sizer.SetSizeHints(self)
513 503 self.SetSizer(sizer)
514 504 #and we focus on the widget :)
515 505 self.SetFocus()
516 506
517 507 #widget state management (for key handling different cases)
518 508 self.setCurrentState('IDLE')
519 509 self.pager_state = 'DONE'
520 510
521 511 def askExitCallback(self, event):
522 512 self.askExitHandler(event)
523 513
524 514 #---------------------- IPython Thread Management ------------------------
525 515 def stateDoExecuteLine(self):
526 516 #print >>sys.__stdout__,"command:",self.getCurrentLine()
527 517 line=self.text_ctrl.getCurrentLine()
518 self.text_ctrl.write('\n')
528 519 self.IP.doExecute((line.replace('\t',' '*4)).encode('cp1252'))
529 520 self.updateHistoryTracker(self.text_ctrl.getCurrentLine())
530 521 self.setCurrentState('WAIT_END_OF_EXECUTION')
531 522
532 523 def evtStateExecuteDone(self,evt):
533 524 self.doc = self.IP.getDocText()
534 525 self.help = self.IP.getHelpText()
535 526 if self.doc:
536 527 self.pager_lines = self.doc[7:].split('\n')
537 528 self.pager_state = 'INIT'
538 529 self.setCurrentState('SHOW_DOC')
539 530 self.pager(self.doc)
540 531 elif self.help:
541 532 self.pager_lines = self.help.split('\n')
542 533 self.pager_state = 'INIT'
543 534 self.setCurrentState('SHOW_DOC')
544 535 self.pager(self.help)
545 536 else:
546 537 self.stateShowPrompt()
547 538
548 539 def stateShowPrompt(self):
549 540 self.setCurrentState('SHOW_PROMPT')
550 541 self.text_ctrl.setPrompt(self.IP.getPrompt())
551 542 self.text_ctrl.setIndentation(self.IP.getIndentation())
552 543 self.text_ctrl.setPromptCount(self.IP.getPromptCount())
553 rv = self.cout.getvalue()
554 if rv: rv = rv.strip('\n')
555 self.text_ctrl.showReturned(rv)
556 self.cout.truncate(0)
544 self.text_ctrl.showPrompt()
557 545 self.IP.initHistoryIndex()
558 546 self.setCurrentState('IDLE')
559 547
560 548 def setCurrentState(self, state):
561 549 self.cur_state = state
562 550 self.updateStatusTracker(self.cur_state)
563 551
564 552 #---------------------------- IPython pager ---------------------------------------
565 553 def pager(self,text):#,start=0,screen_lines=0,pager_cmd = None):
566 554
567 555 if self.pager_state == 'INIT':
568 556 #print >>sys.__stdout__,"PAGER state:",self.pager_state
569 557 self.pager_nb_lines = len(self.pager_lines)
570 558 self.pager_index = 0
571 559 self.pager_do_remove = False
572 560 self.text_ctrl.write('\n')
573 561 self.pager_state = 'PROCESS_LINES'
574 562
575 563 if self.pager_state == 'PROCESS_LINES':
576 564 #print >>sys.__stdout__,"PAGER state:",self.pager_state
577 565 if self.pager_do_remove == True:
578 566 self.text_ctrl.removeCurrentLine()
579 567 self.pager_do_remove = False
580 568
581 569 if self.pager_nb_lines > 10:
582 570 #print >>sys.__stdout__,"PAGER processing 10 lines"
583 571 if self.pager_index > 0:
584 572 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
585 573 else:
586 574 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
587 575
588 576 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
589 577 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
590 578 self.pager_index += 10
591 579 self.pager_nb_lines -= 10
592 580 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
593 581 self.pager_do_remove = True
594 582 self.pager_state = 'WAITING'
595 583 return
596 584 else:
597 585 #print >>sys.__stdout__,"PAGER processing last lines"
598 586 if self.pager_nb_lines > 0:
599 587 if self.pager_index > 0:
600 588 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
601 589 else:
602 590 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
603 591
604 592 self.pager_index += 1
605 593 self.pager_nb_lines -= 1
606 594 if self.pager_nb_lines > 0:
607 595 for line in self.pager_lines[self.pager_index:]:
608 596 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
609 597 self.pager_nb_lines = 0
610 598 self.pager_state = 'DONE'
611 599 self.stateShowPrompt()
612 600
613 601 #------------------------ Key Handler ------------------------------------
614 602 def keyPress(self, event):
615 603 '''
616 604 Key press callback with plenty of shell goodness, like history,
617 605 autocompletions, etc.
618 606 '''
619 607
620 608 if event.GetKeyCode() == ord('C'):
621 609 if event.Modifiers == wx.MOD_CONTROL:
622 610 if self.cur_state == 'WAIT_END_OF_EXECUTION':
623 611 #we raise an exception inside the IPython thread container
624 612 self.IP.ce.raise_exc(KeyboardInterrupt)
625 613 return
626 614
627 615 if event.KeyCode == wx.WXK_RETURN:
628 616 if self.cur_state == 'IDLE':
629 617 #we change the state ot the state machine
630 618 self.setCurrentState('DO_EXECUTE_LINE')
631 619 self.stateDoExecuteLine()
632 620 return
633 621 if self.pager_state == 'WAITING':
634 622 self.pager_state = 'PROCESS_LINES'
635 623 self.pager(self.doc)
636 624 return
637 625
638 626 if event.GetKeyCode() in [ord('q'),ord('Q')]:
639 627 if self.pager_state == 'WAITING':
640 628 self.pager_state = 'DONE'
641 629 self.stateShowPrompt()
642 630 return
643
644 #scroll_position = self.text_ctrl.GetScrollPos(wx.VERTICAL)
631
632 # if self.cur_state == 'WAIT_END_OF_EXECUTION':
633 # print self.cur_state
634 # self.text_ctrl.write('.')
635
645 636 if self.cur_state == 'IDLE':
646 637 if event.KeyCode == wx.WXK_UP:
647 638 history = self.IP.historyBack()
648 639 self.text_ctrl.writeHistory(history)
649 640 return
650 641 if event.KeyCode == wx.WXK_DOWN:
651 642 history = self.IP.historyForward()
652 643 self.text_ctrl.writeHistory(history)
653 644 return
654 645 if event.KeyCode == wx.WXK_TAB:
655 646 #if line empty we disable tab completion
656 647 if not self.text_ctrl.getCurrentLine().strip():
657 648 self.text_ctrl.write('\t')
658 649 return
659 650 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
660 651 if len(possibilities) > 1:
661 652 cur_slice = self.text_ctrl.getCurrentLine()
662 653 self.text_ctrl.write('\n')
663 654 self.text_ctrl.writeCompletion(possibilities)
664 655 self.text_ctrl.write('\n')
665 656
666 657 self.text_ctrl.showPrompt()
667 658 self.text_ctrl.write(cur_slice)
668 659 self.text_ctrl.changeLine(completed or cur_slice)
669 660
670 661 return
671 662 event.Skip()
672
663
673 664 #------------------------ Hook Section -----------------------------------
674 665 def updateHistoryTracker(self,command_line):
675 666 '''
676 667 Default history tracker (does nothing)
677 668 '''
678 669 pass
679 670
680 671 def setHistoryTrackerHook(self,func):
681 672 '''
682 673 Define a new history tracker
683 674 '''
684 675 self.updateHistoryTracker = func
685 676
686 677 def updateStatusTracker(self,status):
687 678 '''
688 679 Default status tracker (does nothing)
689 680 '''
690 681 pass
691 682
692 683 def setStatusTrackerHook(self,func):
693 684 '''
694 685 Define a new status tracker
695 686 '''
696 687 self.updateStatusTracker = func
697 688
698 689 def askExitHandler(self, event):
699 690 '''
700 691 Default exit handler
701 692 '''
702 693 self.text_ctrl.write('\nExit callback has not been set.')
703 694
704 695 def setAskExitHandler(self, func):
705 696 '''
706 697 Define an exit handler
707 698 '''
708 699 self.askExitHandler = func
709 700
710 701 if __name__ == '__main__':
711 702 # Some simple code to test the shell widget.
712 703 class MainWindow(wx.Frame):
713 704 def __init__(self, parent, id, title):
714 705 wx.Frame.__init__(self, parent, id, title, size=(300,250))
715 706 self._sizer = wx.BoxSizer(wx.VERTICAL)
716 707 self.shell = IPShellWidget(self)
717 708 self._sizer.Add(self.shell, 1, wx.EXPAND)
718 709 self.SetSizer(self._sizer)
719 710 self.SetAutoLayout(1)
720 711 self.Show(True)
721 712
722 713 app = wx.PySimpleApp()
723 714 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
724 715 frame.SetSize((780, 460))
725 716 shell = frame.shell
726 717
727 718 app.MainLoop()
728 719
729 720
@@ -1,202 +1,201 b''
1 1 #!/usr/bin/python
2 2 # -*- coding: iso-8859-15 -*-
3 3
4 4 import wx.aui
5 5
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 13 __version__ = 0.8
14 14 __author__ = "Laurent Dufrechou"
15 15 __email__ = "laurent.dufrechou _at_ gmail.com"
16 16 __license__ = "BSD"
17 17
18 18 #-----------------------------------------
19 19 # Creating one main frame for our
20 20 # application with movables windows
21 21 #-----------------------------------------
22 22 class MyFrame(wx.Frame):
23 23 """Creating one main frame for our
24 24 application with movables windows"""
25 25 def __init__(self, parent=None, id=-1, title="WxIPython",
26 26 pos=wx.DefaultPosition,
27 27 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
28 28 wx.Frame.__init__(self, parent, id, title, pos, size, style)
29 29 self._mgr = wx.aui.AuiManager()
30 30
31 31 # notify PyAUI which frame to use
32 32 self._mgr.SetManagedWindow(self)
33 33
34 34 #create differents panels and make them persistant
35 35 self.history_panel = IPythonHistoryPanel(self)
36 36
37 37 self.ipython_panel = IPShellWidget(self,background_color = "BLACK")
38 38
39 #self.ipython_panel = WxIPythonViewPanel(self,
40 # background_color = "WHITE")
39 #self.ipython_panel = IPShellWidget(self,background_color = "WHITE")
41 40
42 41 self.ipython_panel.setHistoryTrackerHook(self.history_panel.write)
43 42 self.ipython_panel.setStatusTrackerHook(self.updateStatus)
44 43 self.ipython_panel.setAskExitHandler(self.OnExitDlg)
45 44
46 45 self.statusbar = self.createStatus()
47 46 self.createMenu()
48 47
49 48 ########################################################################
50 49 ### add the panes to the manager
51 50 # main panels
52 51 self._mgr.AddPane(self.ipython_panel , wx.CENTER, "IPython Shell")
53 52 self._mgr.AddPane(self.history_panel , wx.RIGHT, "IPython history")
54 53
55 54 # now we specify some panel characteristics
56 55 self._mgr.GetPane(self.ipython_panel).CaptionVisible(True);
57 56 self._mgr.GetPane(self.history_panel).CaptionVisible(True);
58 57 self._mgr.GetPane(self.history_panel).MinSize((200,400));
59 58
60 59 # tell the manager to "commit" all the changes just made
61 60 self._mgr.Update()
62 61
63 62 #global event handling
64 63 self.Bind(wx.EVT_CLOSE, self.OnClose)
65 64 self.Bind(wx.EVT_MENU, self.OnClose,id=wx.ID_EXIT)
66 65 self.Bind(wx.EVT_MENU, self.OnShowIPythonPanel,id=wx.ID_HIGHEST+1)
67 66 self.Bind(wx.EVT_MENU, self.OnShowHistoryPanel,id=wx.ID_HIGHEST+2)
68 67 self.Bind(wx.EVT_MENU, self.OnShowAbout, id=wx.ID_HIGHEST+3)
69 68 self.Bind(wx.EVT_MENU, self.OnShowAllPanel,id=wx.ID_HIGHEST+6)
70 69
71 70 warn_text = 'Hello from IPython and wxPython.\n'
72 71 warn_text +='Please Note that this work is still EXPERIMENTAL\n'
73 72 warn_text +='It does NOT emulate currently all the IPython functions.\n'
74 73
75 74 dlg = wx.MessageDialog(self,
76 75 warn_text,
77 76 'Warning Box',
78 77 wx.OK | wx.ICON_INFORMATION
79 78 )
80 79 dlg.ShowModal()
81 80 dlg.Destroy()
82 81
83 82 def createMenu(self):
84 83 """local method used to create one menu bar"""
85 84
86 85 mb = wx.MenuBar()
87 86
88 87 file_menu = wx.Menu()
89 88 file_menu.Append(wx.ID_EXIT, "Exit")
90 89
91 90 view_menu = wx.Menu()
92 91 view_menu.Append(wx.ID_HIGHEST+1, "Show IPython Panel")
93 92 view_menu.Append(wx.ID_HIGHEST+2, "Show History Panel")
94 93 view_menu.AppendSeparator()
95 94 view_menu.Append(wx.ID_HIGHEST+6, "Show All")
96 95
97 96 about_menu = wx.Menu()
98 97 about_menu.Append(wx.ID_HIGHEST+3, "About")
99 98
100 99 #view_menu.AppendSeparator()
101 100 #options_menu = wx.Menu()
102 101 #options_menu.AppendCheckItem(wx.ID_HIGHEST+7, "Allow Floating")
103 102 #options_menu.AppendCheckItem(wx.ID_HIGHEST+8, "Transparent Hint")
104 103 #options_menu.AppendCheckItem(wx.ID_HIGHEST+9, "Transparent Hint Fade-in")
105 104
106 105
107 106 mb.Append(file_menu, "File")
108 107 mb.Append(view_menu, "View")
109 108 mb.Append(about_menu, "About")
110 109 #mb.Append(options_menu, "Options")
111 110
112 111 self.SetMenuBar(mb)
113 112
114 113 def createStatus(self):
115 114 statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
116 115 statusbar.SetStatusWidths([-2, -3])
117 116 statusbar.SetStatusText("Ready", 0)
118 117 statusbar.SetStatusText("WxIPython "+str(__version__), 1)
119 118 return statusbar
120 119
121 120 def updateStatus(self,text):
122 121 states = {'IDLE':'Idle',
123 122 'DO_EXECUTE_LINE':'Send command',
124 123 'WAIT_END_OF_EXECUTION':'Running command',
125 124 'SHOW_DOC':'Showing doc',
126 125 'SHOW_PROMPT':'Showing prompt'}
127 126 self.statusbar.SetStatusText(states[text], 0)
128 127
129 128 def OnClose(self, event):
130 129 """#event used to close program """
131 130 # deinitialize the frame manager
132 131 self._mgr.UnInit()
133 132 self.Destroy()
134 133 event.Skip()
135 134
136 135 def OnExitDlg(self, event):
137 136 dlg = wx.MessageDialog(self, 'Are you sure you want to quit WxIPython',
138 137 'WxIPython exit',
139 138 wx.ICON_QUESTION |
140 139 wx.YES_NO | wx.NO_DEFAULT
141 140 )
142 141 if dlg.ShowModal() == wx.ID_YES:
143 142 dlg.Destroy()
144 143 self._mgr.UnInit()
145 144 self.Destroy()
146 145 dlg.Destroy()
147 146
148 147 #event to display IPython pannel
149 148 def OnShowIPythonPanel(self,event):
150 149 """ #event to display Boxpannel """
151 150 self._mgr.GetPane(self.ipython_panel).Show(True)
152 151 self._mgr.Update()
153 152 #event to display History pannel
154 153 def OnShowHistoryPanel(self,event):
155 154 self._mgr.GetPane(self.history_panel).Show(True)
156 155 self._mgr.Update()
157 156
158 157 def OnShowAllPanel(self,event):
159 158 """#event to display all Pannels"""
160 159 self._mgr.GetPane(self.ipython_panel).Show(True)
161 160 self._mgr.GetPane(self.history_panel).Show(True)
162 161 self._mgr.Update()
163 162
164 163 def OnShowAbout(self, event):
165 164 # First we create and fill the info object
166 165 info = wx.AboutDialogInfo()
167 166 info.Name = "WxIPython"
168 167 info.Version = str(__version__)
169 168 info.Copyright = "(C) 2007 Laurent Dufrechou"
170 169 info.Description = wordwrap(
171 170 "A Gui that embbed a multithreaded IPython Shell",
172 171 350, wx.ClientDC(self))
173 172 info.WebSite = ("http://ipython.scipy.org/", "IPython home page")
174 173 info.Developers = [ "Laurent Dufrechou" ]
175 174 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"
176 175 info.License = wordwrap(licenseText, 500, wx.ClientDC(self))
177 176
178 177 # Then we call wx.AboutBox giving it that info object
179 178 wx.AboutBox(info)
180 179
181 180 #-----------------------------------------
182 181 #Creating our application
183 182 #-----------------------------------------
184 183 class MyApp(wx.PySimpleApp):
185 184 """Creating our application"""
186 185 def __init__(self):
187 186 wx.PySimpleApp.__init__(self)
188 187
189 188 self.frame = MyFrame()
190 189 self.frame.Show()
191 190
192 191 #-----------------------------------------
193 192 #Main loop
194 193 #-----------------------------------------
195 194 def main():
196 195 app = MyApp()
197 196 app.SetTopWindow(app.frame)
198 197 app.MainLoop()
199 198
200 199 #if launched as main program run this
201 200 if __name__ == '__main__':
202 201 main()
General Comments 0
You need to be logged in to leave comments. Login now