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