##// END OF EJS Templates
merge
Ville M. Vainio -
r1132:999d4d56 merge
parent child Browse files
Show More
@@ -1,715 +1,729 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 305 def showReturned(self, text):
306 306 '''
307 307 Show returned text from last command and print new prompt.
308 308
309 309 @param text: Text to show.
310 310 @type text: string
311 311 '''
312 312 self.write('\n'+text)
313 313 if text:
314 314 self.write('\n')
315 315 self.showPrompt()
316 316
317 317 def moveCursorOnNewValidKey(self):
318 318 #If cursor is at wrong position put it at last line...
319 319 if self.GetCurrentPos() < self.getCurrentPromptStart():
320 320 self.GotoPos(self.getCurrentPromptStart())
321 321
322 322 def removeFromTo(self,from_pos,to_pos):
323 323 if from_pos < to_pos:
324 324 self.SetSelection(from_pos,to_pos)
325 325 self.DeleteBack()
326 326
327 327 def removeCurrentLine(self):
328 328 self.LineDelete()
329 329
330 330 def moveCursor(self,position):
331 331 self.GotoPos(position)
332 332
333 333 def getCursorPos(self):
334 334 return self.GetCurrentPos()
335 335
336 336 def selectFromTo(self,from_pos,to_pos):
337 337 self.SetSelectionStart(from_pos)
338 338 self.SetSelectionEnd(to_pos)
339 339
340 340 def writeHistory(self,history):
341 341 self.removeFromTo(self.getCurrentPromptStart(),self.getCurrentLineEnd())
342 342 self.changeLine(history)
343 343
344 344 def writeCompletion(self, possibilities):
345 345 max_len = len(max(possibilities,key=len))
346 346 max_symbol =' '*max_len
347 347
348 348 #now we check how much symbol we can put on a line...
349 349 cursor_pos = self.getCursorPos()
350 350 test_buffer = max_symbol + ' '*4
351 351 current_lines = self.GetLineCount()
352 352
353 353 allowed_symbols = 80/len(test_buffer)
354 354 if allowed_symbols == 0:
355 355 allowed_symbols = 1
356 356
357 357 pos = 1
358 358 buf = ''
359 359 for symbol in possibilities:
360 360 #buf += symbol+'\n'#*spaces)
361 361 if pos<allowed_symbols:
362 362 spaces = max_len - len(symbol) + 4
363 363 buf += symbol+' '*spaces
364 364 pos += 1
365 365 else:
366 366 buf+=symbol+'\n'
367 367 pos = 1
368 368 self.write(buf)
369 369
370 370 def _onKeypress(self, event, skip=True):
371 371 '''
372 372 Key press callback used for correcting behavior for console-like
373 373 interfaces. For example 'home' should go to prompt, not to begining of
374 374 line.
375 375
376 376 @param widget: Widget that key press accored in.
377 377 @type widget: gtk.Widget
378 378 @param event: Event object
379 379 @type event: gtk.gdk.Event
380 380
381 381 @return: Return True if event as been catched.
382 382 @rtype: boolean
383 383 '''
384 384
385 385 if event.GetKeyCode() == wx.WXK_HOME:
386 386 if event.Modifiers == wx.MOD_NONE:
387 387 self.moveCursorOnNewValidKey()
388 388 self.moveCursor(self.getCurrentPromptStart())
389 389 return True
390 390 elif event.Modifiers == wx.MOD_SHIFT:
391 391 self.moveCursorOnNewValidKey()
392 392 self.selectFromTo(self.getCurrentPromptStart(),self.getCursorPos())
393 393 return True
394 394 else:
395 395 return False
396 396
397 397 elif event.GetKeyCode() == wx.WXK_LEFT:
398 398 if event.Modifiers == wx.MOD_NONE:
399 399 self.moveCursorOnNewValidKey()
400 400
401 401 self.moveCursor(self.getCursorPos()-1)
402 402 if self.getCursorPos() < self.getCurrentPromptStart():
403 403 self.moveCursor(self.getCurrentPromptStart())
404 404 return True
405 405
406 406 elif event.GetKeyCode() == wx.WXK_BACK:
407 407 self.moveCursorOnNewValidKey()
408 408 if self.getCursorPos() > self.getCurrentPromptStart():
409 409 self.removeFromTo(self.getCursorPos()-1,self.getCursorPos())
410 410 return True
411 411
412 412 if skip:
413 413 if event.GetKeyCode() not in [wx.WXK_PAGEUP,wx.WXK_PAGEDOWN] and event.Modifiers == wx.MOD_NONE:
414 414 self.moveCursorOnNewValidKey()
415 415
416 416 event.Skip()
417 417 return True
418 418 return False
419 419
420 420 def OnUpdateUI(self, evt):
421 421 # check for matching braces
422 422 braceAtCaret = -1
423 423 braceOpposite = -1
424 424 charBefore = None
425 425 caretPos = self.GetCurrentPos()
426 426
427 427 if caretPos > 0:
428 428 charBefore = self.GetCharAt(caretPos - 1)
429 429 styleBefore = self.GetStyleAt(caretPos - 1)
430 430
431 431 # check before
432 432 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
433 433 braceAtCaret = caretPos - 1
434 434
435 435 # check after
436 436 if braceAtCaret < 0:
437 437 charAfter = self.GetCharAt(caretPos)
438 438 styleAfter = self.GetStyleAt(caretPos)
439 439
440 440 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
441 441 braceAtCaret = caretPos
442 442
443 443 if braceAtCaret >= 0:
444 444 braceOpposite = self.BraceMatch(braceAtCaret)
445 445
446 446 if braceAtCaret != -1 and braceOpposite == -1:
447 447 self.BraceBadLight(braceAtCaret)
448 448 else:
449 449 self.BraceHighlight(braceAtCaret, braceOpposite)
450 450 #pt = self.PointFromPosition(braceOpposite)
451 451 #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
452 452 #print pt
453 453 #self.Refresh(False)
454 454
455 455 class IPShellWidget(wx.Panel):
456 456 '''
457 457 This is wx.Panel that embbed the IPython Thread and the wx.StyledTextControl
458 458 If you want to port this to any other GUI toolkit, just replace the
459 459 WxConsoleView by YOURGUIConsoleView and make YOURGUIIPythonView derivate
460 460 from whatever container you want. I've choosed to derivate from a wx.Panel
461 461 because it seems to be more useful
462 462 Any idea to make it more 'generic' welcomed.
463 463 '''
464
465 def __init__(self, parent, ask_exit_handler=None, intro=None,
464 def __init__(self, parent, intro=None,
466 465 background_color="BLACK", add_button_handler=None,
467 466 wx_ip_shell=None,
468 467 ):
469 468 '''
470 469 Initialize.
471 470 Instanciate an IPython thread.
472 471 Instanciate a WxConsoleView.
473 472 Redirect I/O to console.
474 473 '''
475 474 wx.Panel.__init__(self,parent,-1)
476 475
477 476 ### IPython non blocking shell instanciation ###
478 477 self.cout = StringIO()
479 478
480 479 self.add_button_handler = add_button_handler
481 self.ask_exit_handler = ask_exit_handler
482 480
483 481 if wx_ip_shell is not None:
484 482 self.IP = wx_ip_shell
485 483 else:
486 484 self.IP = WxNonBlockingIPShell(self,
487 cout=self.cout,cerr=self.cout,
488 ask_exit_handler = ask_exit_handler)
485 cout = self.cout,cerr = self.cout,
486 ask_exit_handler = self.askExitCallback)
489 487
490 488 ### IPython wx console view instanciation ###
491 489 #If user didn't defined an intro text, we create one for him
492 490 #If you really wnat an empty intrp just call wxIPythonViewPanel
493 491 #with intro=''
494 if intro == None:
492 if intro is None:
495 493 welcome_text = "Welcome to WxIPython Shell.\n\n"
496 494 welcome_text+= self.IP.getBanner()
497 495 welcome_text+= "!command -> Execute command in shell\n"
498 496 welcome_text+= "TAB -> Autocompletion\n"
497 else:
498 welcome_text = intro
499 499
500 500 self.text_ctrl = WxConsoleView(self,
501 501 self.IP.getPrompt(),
502 502 intro=welcome_text,
503 503 background_color=background_color)
504 504
505 505 self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.keyPress, self.text_ctrl)
506 506
507 507 ### making the layout of the panel ###
508 508 sizer = wx.BoxSizer(wx.VERTICAL)
509 509 sizer.Add(self.text_ctrl, 1, wx.EXPAND)
510 510 self.SetAutoLayout(True)
511 511 sizer.Fit(self)
512 512 sizer.SetSizeHints(self)
513 513 self.SetSizer(sizer)
514 514 #and we focus on the widget :)
515 515 self.SetFocus()
516 516
517 517 #widget state management (for key handling different cases)
518 518 self.setCurrentState('IDLE')
519 519 self.pager_state = 'DONE'
520 520
521 def askExitCallback(self, event):
522 self.askExitHandler(event)
523
521 524 #---------------------- IPython Thread Management ------------------------
522 525 def stateDoExecuteLine(self):
523 526 #print >>sys.__stdout__,"command:",self.getCurrentLine()
524 527 line=self.text_ctrl.getCurrentLine()
525 528 self.IP.doExecute((line.replace('\t',' '*4)).encode('cp1252'))
526 529 self.updateHistoryTracker(self.text_ctrl.getCurrentLine())
527 530 self.setCurrentState('WAIT_END_OF_EXECUTION')
528 531
529 532 def evtStateExecuteDone(self,evt):
530 533 self.doc = self.IP.getDocText()
531 534 self.help = self.IP.getHelpText()
532 535 if self.doc:
533 536 self.pager_lines = self.doc[7:].split('\n')
534 537 self.pager_state = 'INIT'
535 538 self.setCurrentState('SHOW_DOC')
536 539 self.pager(self.doc)
537 540 elif self.help:
538 541 self.pager_lines = self.help.split('\n')
539 542 self.pager_state = 'INIT'
540 543 self.setCurrentState('SHOW_DOC')
541 544 self.pager(self.help)
542 545 else:
543 546 self.stateShowPrompt()
544 547
545 548 def stateShowPrompt(self):
546 549 self.setCurrentState('SHOW_PROMPT')
547 550 self.text_ctrl.setPrompt(self.IP.getPrompt())
548 551 self.text_ctrl.setIndentation(self.IP.getIndentation())
549 552 self.text_ctrl.setPromptCount(self.IP.getPromptCount())
550 553 rv = self.cout.getvalue()
551 554 if rv: rv = rv.strip('\n')
552 555 self.text_ctrl.showReturned(rv)
553 556 self.cout.truncate(0)
554 557 self.IP.initHistoryIndex()
555 558 self.setCurrentState('IDLE')
556 559
557 560 def setCurrentState(self, state):
558 561 self.cur_state = state
559 562 self.updateStatusTracker(self.cur_state)
560 563
561 564 #---------------------------- IPython pager ---------------------------------------
562 565 def pager(self,text):#,start=0,screen_lines=0,pager_cmd = None):
563 566
564 567 if self.pager_state == 'INIT':
565 568 #print >>sys.__stdout__,"PAGER state:",self.pager_state
566 569 self.pager_nb_lines = len(self.pager_lines)
567 570 self.pager_index = 0
568 571 self.pager_do_remove = False
569 572 self.text_ctrl.write('\n')
570 573 self.pager_state = 'PROCESS_LINES'
571 574
572 575 if self.pager_state == 'PROCESS_LINES':
573 576 #print >>sys.__stdout__,"PAGER state:",self.pager_state
574 577 if self.pager_do_remove == True:
575 578 self.text_ctrl.removeCurrentLine()
576 579 self.pager_do_remove = False
577 580
578 581 if self.pager_nb_lines > 10:
579 582 #print >>sys.__stdout__,"PAGER processing 10 lines"
580 583 if self.pager_index > 0:
581 584 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
582 585 else:
583 586 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
584 587
585 588 for line in self.pager_lines[self.pager_index+1:self.pager_index+9]:
586 589 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
587 590 self.pager_index += 10
588 591 self.pager_nb_lines -= 10
589 592 self.text_ctrl.write("--- Push Enter to continue or 'Q' to quit---")
590 593 self.pager_do_remove = True
591 594 self.pager_state = 'WAITING'
592 595 return
593 596 else:
594 597 #print >>sys.__stdout__,"PAGER processing last lines"
595 598 if self.pager_nb_lines > 0:
596 599 if self.pager_index > 0:
597 600 self.text_ctrl.write(">\x01\x1b[1;36m\x02"+self.pager_lines[self.pager_index]+'\n')
598 601 else:
599 602 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+self.pager_lines[self.pager_index]+'\n')
600 603
601 604 self.pager_index += 1
602 605 self.pager_nb_lines -= 1
603 606 if self.pager_nb_lines > 0:
604 607 for line in self.pager_lines[self.pager_index:]:
605 608 self.text_ctrl.write("\x01\x1b[1;36m\x02 "+line+'\n')
606 609 self.pager_nb_lines = 0
607 610 self.pager_state = 'DONE'
608 611 self.stateShowPrompt()
609 612
610 613 #------------------------ Key Handler ------------------------------------
611 614 def keyPress(self, event):
612 615 '''
613 616 Key press callback with plenty of shell goodness, like history,
614 617 autocompletions, etc.
615 618 '''
616 619
617 620 if event.GetKeyCode() == ord('C'):
618 621 if event.Modifiers == wx.MOD_CONTROL:
619 622 if self.cur_state == 'WAIT_END_OF_EXECUTION':
620 623 #we raise an exception inside the IPython thread container
621 624 self.IP.ce.raise_exc(KeyboardInterrupt)
622 625 return
623 626
624 627 if event.KeyCode == wx.WXK_RETURN:
625 628 if self.cur_state == 'IDLE':
626 629 #we change the state ot the state machine
627 630 self.setCurrentState('DO_EXECUTE_LINE')
628 631 self.stateDoExecuteLine()
629 632 return
630 633 if self.pager_state == 'WAITING':
631 634 self.pager_state = 'PROCESS_LINES'
632 635 self.pager(self.doc)
633 636 return
634 637
635 638 if event.GetKeyCode() in [ord('q'),ord('Q')]:
636 639 if self.pager_state == 'WAITING':
637 640 self.pager_state = 'DONE'
638 641 self.stateShowPrompt()
639 642 return
640 643
641 644 #scroll_position = self.text_ctrl.GetScrollPos(wx.VERTICAL)
642 645 if self.cur_state == 'IDLE':
643 646 if event.KeyCode == wx.WXK_UP:
644 647 history = self.IP.historyBack()
645 648 self.text_ctrl.writeHistory(history)
646 649 return
647 650 if event.KeyCode == wx.WXK_DOWN:
648 651 history = self.IP.historyForward()
649 652 self.text_ctrl.writeHistory(history)
650 653 return
651 654 if event.KeyCode == wx.WXK_TAB:
652 655 #if line empty we disable tab completion
653 656 if not self.text_ctrl.getCurrentLine().strip():
654 657 self.text_ctrl.write('\t')
655 658 return
656 659 completed, possibilities = self.IP.complete(self.text_ctrl.getCurrentLine())
657 660 if len(possibilities) > 1:
658 661 cur_slice = self.text_ctrl.getCurrentLine()
659 662 self.text_ctrl.write('\n')
660 663 self.text_ctrl.writeCompletion(possibilities)
661 664 self.text_ctrl.write('\n')
662 665
663 666 self.text_ctrl.showPrompt()
664 667 self.text_ctrl.write(cur_slice)
665 668 self.text_ctrl.changeLine(completed or cur_slice)
666 669
667 670 return
668 671 event.Skip()
669 672
670 673 #------------------------ Hook Section -----------------------------------
671 674 def updateHistoryTracker(self,command_line):
672 675 '''
673 676 Default history tracker (does nothing)
674 677 '''
675 678 pass
676 679
677 680 def setHistoryTrackerHook(self,func):
678 681 '''
679 682 Define a new history tracker
680 683 '''
681 684 self.updateHistoryTracker = func
682 685
683 686 def updateStatusTracker(self,status):
684 687 '''
685 688 Default status tracker (does nothing)
686 689 '''
687 690 pass
688 691
689 692 def setStatusTrackerHook(self,func):
690 693 '''
691 694 Define a new status tracker
692 695 '''
693 696 self.updateStatusTracker = func
694 697
698 def askExitHandler(self, event):
699 '''
700 Default exit handler
701 '''
702 self.text_ctrl.write('\nExit callback has not been set.')
703
704 def setAskExitHandler(self, func):
705 '''
706 Define an exit handler
707 '''
708 self.askExitHandler = func
695 709
696 710 if __name__ == '__main__':
697 711 # Some simple code to test the shell widget.
698 712 class MainWindow(wx.Frame):
699 713 def __init__(self, parent, id, title):
700 714 wx.Frame.__init__(self, parent, id, title, size=(300,250))
701 715 self._sizer = wx.BoxSizer(wx.VERTICAL)
702 716 self.shell = IPShellWidget(self)
703 717 self._sizer.Add(self.shell, 1, wx.EXPAND)
704 718 self.SetSizer(self._sizer)
705 719 self.SetAutoLayout(1)
706 720 self.Show(True)
707 721
708 722 app = wx.PySimpleApp()
709 723 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
710 724 frame.SetSize((780, 460))
711 725 shell = frame.shell
712 726
713 727 app.MainLoop()
714 728
715 729
@@ -1,202 +1,202 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 self.ipython_panel = IPShellWidget(self,self.OnExitDlg,
38 background_color = "BLACK")
37 self.ipython_panel = IPShellWidget(self,background_color = "BLACK")
39 38
40 #self.ipython_panel = WxIPythonViewPanel(self,self.OnExitDlg,
39 #self.ipython_panel = WxIPythonViewPanel(self,
41 40 # background_color = "WHITE")
42 41
43 42 self.ipython_panel.setHistoryTrackerHook(self.history_panel.write)
44 43 self.ipython_panel.setStatusTrackerHook(self.updateStatus)
44 self.ipython_panel.setAskExitHandler(self.OnExitDlg)
45 45
46 46 self.statusbar = self.createStatus()
47 47 self.createMenu()
48 48
49 49 ########################################################################
50 50 ### add the panes to the manager
51 51 # main panels
52 52 self._mgr.AddPane(self.ipython_panel , wx.CENTER, "IPython Shell")
53 53 self._mgr.AddPane(self.history_panel , wx.RIGHT, "IPython history")
54 54
55 55 # now we specify some panel characteristics
56 56 self._mgr.GetPane(self.ipython_panel).CaptionVisible(True);
57 57 self._mgr.GetPane(self.history_panel).CaptionVisible(True);
58 58 self._mgr.GetPane(self.history_panel).MinSize((200,400));
59 59
60 60 # tell the manager to "commit" all the changes just made
61 61 self._mgr.Update()
62 62
63 63 #global event handling
64 64 self.Bind(wx.EVT_CLOSE, self.OnClose)
65 65 self.Bind(wx.EVT_MENU, self.OnClose,id=wx.ID_EXIT)
66 66 self.Bind(wx.EVT_MENU, self.OnShowIPythonPanel,id=wx.ID_HIGHEST+1)
67 67 self.Bind(wx.EVT_MENU, self.OnShowHistoryPanel,id=wx.ID_HIGHEST+2)
68 68 self.Bind(wx.EVT_MENU, self.OnShowAbout, id=wx.ID_HIGHEST+3)
69 69 self.Bind(wx.EVT_MENU, self.OnShowAllPanel,id=wx.ID_HIGHEST+6)
70 70
71 71 warn_text = 'Hello from IPython and wxPython.\n'
72 72 warn_text +='Please Note that this work is still EXPERIMENTAL\n'
73 73 warn_text +='It does NOT emulate currently all the IPython functions.\n'
74 74
75 75 dlg = wx.MessageDialog(self,
76 76 warn_text,
77 77 'Warning Box',
78 78 wx.OK | wx.ICON_INFORMATION
79 79 )
80 80 dlg.ShowModal()
81 81 dlg.Destroy()
82 82
83 83 def createMenu(self):
84 84 """local method used to create one menu bar"""
85 85
86 86 mb = wx.MenuBar()
87 87
88 88 file_menu = wx.Menu()
89 89 file_menu.Append(wx.ID_EXIT, "Exit")
90 90
91 91 view_menu = wx.Menu()
92 92 view_menu.Append(wx.ID_HIGHEST+1, "Show IPython Panel")
93 93 view_menu.Append(wx.ID_HIGHEST+2, "Show History Panel")
94 94 view_menu.AppendSeparator()
95 95 view_menu.Append(wx.ID_HIGHEST+6, "Show All")
96 96
97 97 about_menu = wx.Menu()
98 98 about_menu.Append(wx.ID_HIGHEST+3, "About")
99 99
100 100 #view_menu.AppendSeparator()
101 101 #options_menu = wx.Menu()
102 102 #options_menu.AppendCheckItem(wx.ID_HIGHEST+7, "Allow Floating")
103 103 #options_menu.AppendCheckItem(wx.ID_HIGHEST+8, "Transparent Hint")
104 104 #options_menu.AppendCheckItem(wx.ID_HIGHEST+9, "Transparent Hint Fade-in")
105 105
106 106
107 107 mb.Append(file_menu, "File")
108 108 mb.Append(view_menu, "View")
109 109 mb.Append(about_menu, "About")
110 110 #mb.Append(options_menu, "Options")
111 111
112 112 self.SetMenuBar(mb)
113 113
114 114 def createStatus(self):
115 115 statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
116 116 statusbar.SetStatusWidths([-2, -3])
117 117 statusbar.SetStatusText("Ready", 0)
118 118 statusbar.SetStatusText("WxIPython "+str(__version__), 1)
119 119 return statusbar
120 120
121 121 def updateStatus(self,text):
122 122 states = {'IDLE':'Idle',
123 123 'DO_EXECUTE_LINE':'Send command',
124 124 'WAIT_END_OF_EXECUTION':'Running command',
125 125 'SHOW_DOC':'Showing doc',
126 126 'SHOW_PROMPT':'Showing prompt'}
127 127 self.statusbar.SetStatusText(states[text], 0)
128 128
129 129 def OnClose(self, event):
130 130 """#event used to close program """
131 131 # deinitialize the frame manager
132 132 self._mgr.UnInit()
133 133 self.Destroy()
134 134 event.Skip()
135 135
136 136 def OnExitDlg(self, event):
137 137 dlg = wx.MessageDialog(self, 'Are you sure you want to quit WxIPython',
138 138 'WxIPython exit',
139 139 wx.ICON_QUESTION |
140 140 wx.YES_NO | wx.NO_DEFAULT
141 141 )
142 142 if dlg.ShowModal() == wx.ID_YES:
143 143 dlg.Destroy()
144 144 self._mgr.UnInit()
145 145 self.Destroy()
146 146 dlg.Destroy()
147 147
148 148 #event to display IPython pannel
149 149 def OnShowIPythonPanel(self,event):
150 150 """ #event to display Boxpannel """
151 151 self._mgr.GetPane(self.ipython_panel).Show(True)
152 152 self._mgr.Update()
153 153 #event to display History pannel
154 154 def OnShowHistoryPanel(self,event):
155 155 self._mgr.GetPane(self.history_panel).Show(True)
156 156 self._mgr.Update()
157 157
158 158 def OnShowAllPanel(self,event):
159 159 """#event to display all Pannels"""
160 160 self._mgr.GetPane(self.ipython_panel).Show(True)
161 161 self._mgr.GetPane(self.history_panel).Show(True)
162 162 self._mgr.Update()
163 163
164 164 def OnShowAbout(self, event):
165 165 # First we create and fill the info object
166 166 info = wx.AboutDialogInfo()
167 167 info.Name = "WxIPython"
168 168 info.Version = str(__version__)
169 169 info.Copyright = "(C) 2007 Laurent Dufrechou"
170 170 info.Description = wordwrap(
171 171 "A Gui that embbed a multithreaded IPython Shell",
172 172 350, wx.ClientDC(self))
173 173 info.WebSite = ("http://ipython.scipy.org/", "IPython home page")
174 174 info.Developers = [ "Laurent Dufrechou" ]
175 175 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 176 info.License = wordwrap(licenseText, 500, wx.ClientDC(self))
177 177
178 178 # Then we call wx.AboutBox giving it that info object
179 179 wx.AboutBox(info)
180 180
181 181 #-----------------------------------------
182 182 #Creating our application
183 183 #-----------------------------------------
184 184 class MyApp(wx.PySimpleApp):
185 185 """Creating our application"""
186 186 def __init__(self):
187 187 wx.PySimpleApp.__init__(self)
188 188
189 189 self.frame = MyFrame()
190 190 self.frame.Show()
191 191
192 192 #-----------------------------------------
193 193 #Main loop
194 194 #-----------------------------------------
195 195 def main():
196 196 app = MyApp()
197 197 app.SetTopWindow(app.frame)
198 198 app.MainLoop()
199 199
200 200 #if launched as main program run this
201 201 if __name__ == '__main__':
202 202 main()
General Comments 0
You need to be logged in to leave comments. Login now