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