##// END OF EJS Templates
Fix the default size under MacOSX.
Gael Varoquaux -
Show More
@@ -1,436 +1,436 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import time
27 27 import sys
28 28 LINESEP = '\n'
29 29 if sys.platform == 'win32':
30 30 LINESEP = '\n\r'
31 31
32 32 import re
33 33
34 34 # FIXME: Need to provide an API for non user-generated display on the
35 35 # screen: this should not be editable by the user.
36 36
37 37 _DEFAULT_SIZE = 10
38 38 if sys.platform == 'darwin':
39 _DEFAULT_STYLE = 14
39 _DEFAULT_SIZE = 12
40 40
41 41 _DEFAULT_STYLE = {
42 42 'stdout' : 'fore:#0000FF',
43 43 'stderr' : 'fore:#007f00',
44 44 'trace' : 'fore:#FF0000',
45 45
46 46 'default' : 'size:%d' % _DEFAULT_SIZE,
47 47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
48 48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
49 49
50 50 # properties for the various Python lexer styles
51 51 'comment' : 'fore:#007F00',
52 52 'number' : 'fore:#007F7F',
53 53 'string' : 'fore:#7F007F,italic',
54 54 'char' : 'fore:#7F007F,italic',
55 55 'keyword' : 'fore:#00007F,bold',
56 56 'triple' : 'fore:#7F0000',
57 57 'tripledouble' : 'fore:#7F0000',
58 58 'class' : 'fore:#0000FF,bold,underline',
59 59 'def' : 'fore:#007F7F,bold',
60 60 'operator' : 'bold'
61 61 }
62 62
63 63 # new style numbers
64 64 _STDOUT_STYLE = 15
65 65 _STDERR_STYLE = 16
66 66 _TRACE_STYLE = 17
67 67
68 68
69 69 # system colors
70 70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
71 71
72 72 #-------------------------------------------------------------------------------
73 73 # The console widget class
74 74 #-------------------------------------------------------------------------------
75 75 class ConsoleWidget(editwindow.EditWindow):
76 76 """ Specialized styled text control view for console-like workflow.
77 77
78 78 This widget is mainly interested in dealing with the prompt and
79 79 keeping the cursor inside the editing line.
80 80 """
81 81
82 82 # This is where the title captured from the ANSI escape sequences are
83 83 # stored.
84 84 title = 'Console'
85 85
86 86 # The buffer being edited.
87 87 def _set_input_buffer(self, string):
88 88 self.SetSelection(self.current_prompt_pos, self.GetLength())
89 89 self.ReplaceSelection(string)
90 90 self.GotoPos(self.GetLength())
91 91
92 92 def _get_input_buffer(self):
93 93 """ Returns the text in current edit buffer.
94 94 """
95 95 input_buffer = self.GetTextRange(self.current_prompt_pos,
96 96 self.GetLength())
97 97 input_buffer = input_buffer.replace(LINESEP, '\n')
98 98 return input_buffer
99 99
100 100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101 101
102 102 style = _DEFAULT_STYLE.copy()
103 103
104 104 # Translation table from ANSI escape sequences to color. Override
105 105 # this to specify your colors.
106 106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
107 107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
108 108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
109 109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
110 110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
111 111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
112 112 '1;34': [12, 'LIGHT BLUE'], '1;35':
113 113 [13, 'MEDIUM VIOLET RED'],
114 114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
115 115
116 116 # The color of the carret (call _apply_style() after setting)
117 117 carret_color = 'BLACK'
118 118
119 119 # Store the last time a refresh was done
120 120 _last_refresh_time = 0
121 121
122 122 #--------------------------------------------------------------------------
123 123 # Public API
124 124 #--------------------------------------------------------------------------
125 125
126 126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 129 self._configure_scintilla()
130 130
131 131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
133 133
134 134
135 135 def write(self, text, refresh=True):
136 136 """ Write given text to buffer, while translating the ansi escape
137 137 sequences.
138 138 """
139 139 # XXX: do not put print statements to sys.stdout/sys.stderr in
140 140 # this method, the print statements will call this method, as
141 141 # you will end up with an infinit loop
142 142 title = self.title_pat.split(text)
143 143 if len(title)>1:
144 144 self.title = title[-2]
145 145
146 146 text = self.title_pat.sub('', text)
147 147 segments = self.color_pat.split(text)
148 148 segment = segments.pop(0)
149 149 self.GotoPos(self.GetLength())
150 150 self.StartStyling(self.GetLength(), 0xFF)
151 151 try:
152 152 self.AppendText(segment)
153 153 except UnicodeDecodeError:
154 154 # XXX: Do I really want to skip the exception?
155 155 pass
156 156
157 157 if segments:
158 158 for ansi_tag, text in zip(segments[::2], segments[1::2]):
159 159 self.StartStyling(self.GetLength(), 0xFF)
160 160 try:
161 161 self.AppendText(text)
162 162 except UnicodeDecodeError:
163 163 # XXX: Do I really want to skip the exception?
164 164 pass
165 165
166 166 if ansi_tag not in self.ANSI_STYLES:
167 167 style = 0
168 168 else:
169 169 style = self.ANSI_STYLES[ansi_tag][0]
170 170
171 171 self.SetStyling(len(text), style)
172 172
173 173 self.GotoPos(self.GetLength())
174 174 if refresh:
175 175 current_time = time.time()
176 176 if current_time - self._last_refresh_time > 0.03:
177 177 if sys.platform == 'win32':
178 178 wx.SafeYield()
179 179 else:
180 180 wx.Yield()
181 181 # self.ProcessEvent(wx.PaintEvent())
182 182 self._last_refresh_time = current_time
183 183
184 184
185 185 def new_prompt(self, prompt):
186 186 """ Prints a prompt at start of line, and move the start of the
187 187 current block there.
188 188
189 189 The prompt can be given with ascii escape sequences.
190 190 """
191 191 self.write(prompt, refresh=False)
192 192 # now we update our cursor giving end of prompt
193 193 self.current_prompt_pos = self.GetLength()
194 194 self.current_prompt_line = self.GetCurrentLine()
195 195 self.EnsureCaretVisible()
196 196
197 197
198 198 def scroll_to_bottom(self):
199 199 maxrange = self.GetScrollRange(wx.VERTICAL)
200 200 self.ScrollLines(maxrange)
201 201
202 202
203 203 def pop_completion(self, possibilities, offset=0):
204 204 """ Pops up an autocompletion menu. Offset is the offset
205 205 in characters of the position at which the menu should
206 206 appear, relativ to the cursor.
207 207 """
208 208 self.AutoCompSetIgnoreCase(False)
209 209 self.AutoCompSetAutoHide(False)
210 210 self.AutoCompSetMaxHeight(len(possibilities))
211 211 self.AutoCompShow(offset, " ".join(possibilities))
212 212
213 213
214 214 def get_line_width(self):
215 215 """ Return the width of the line in characters.
216 216 """
217 217 return self.GetSize()[0]/self.GetCharWidth()
218 218
219 219 #--------------------------------------------------------------------------
220 220 # EditWindow API
221 221 #--------------------------------------------------------------------------
222 222
223 223 def OnUpdateUI(self, event):
224 224 """ Override the OnUpdateUI of the EditWindow class, to prevent
225 225 syntax highlighting both for faster redraw, and for more
226 226 consistent look and feel.
227 227 """
228 228
229 229 #--------------------------------------------------------------------------
230 230 # Private API
231 231 #--------------------------------------------------------------------------
232 232
233 233 def _apply_style(self):
234 234 """ Applies the colors for the different text elements and the
235 235 carret.
236 236 """
237 237 self.SetCaretForeground(self.carret_color)
238 238
239 239 #self.StyleClearAll()
240 240 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
241 241 "fore:#FF0000,back:#0000FF,bold")
242 242 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
243 243 "fore:#000000,back:#FF0000,bold")
244 244
245 245 for style in self.ANSI_STYLES.values():
246 246 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
247 247
248 248
249 249 def _configure_scintilla(self):
250 250 self.SetEOLMode(stc.STC_EOL_LF)
251 251
252 252 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
253 253 # the widget
254 254 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
255 255 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
256 256 # Also allow Ctrl Shift "=" for poor non US keyboard users.
257 257 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
258 258 stc.STC_CMD_ZOOMIN)
259 259
260 260 # Keys: we need to clear some of the keys the that don't play
261 261 # well with a console.
262 262 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
263 263 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
264 264 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
265 265 self.CmdKeyClear(ord('A'), stc.STC_SCMOD_CTRL)
266 266
267 267 self.SetEOLMode(stc.STC_EOL_CRLF)
268 268 self.SetWrapMode(stc.STC_WRAP_CHAR)
269 269 self.SetWrapMode(stc.STC_WRAP_WORD)
270 270 self.SetBufferedDraw(True)
271 271 self.SetUseAntiAliasing(True)
272 272 self.SetLayoutCache(stc.STC_CACHE_PAGE)
273 273 self.SetUndoCollection(False)
274 274 self.SetUseTabs(True)
275 275 self.SetIndent(4)
276 276 self.SetTabWidth(4)
277 277
278 278 # we don't want scintilla's autocompletion to choose
279 279 # automaticaly out of a single choice list, as we pop it up
280 280 # automaticaly
281 281 self.AutoCompSetChooseSingle(False)
282 282 self.AutoCompSetMaxHeight(10)
283 283 # XXX: this doesn't seem to have an effect.
284 284 self.AutoCompSetFillUps('\n')
285 285
286 286 self.SetMargins(3, 3) #text is moved away from border with 3px
287 287 # Suppressing Scintilla margins
288 288 self.SetMarginWidth(0, 0)
289 289 self.SetMarginWidth(1, 0)
290 290 self.SetMarginWidth(2, 0)
291 291
292 292 self._apply_style()
293 293
294 294 # Xterm escape sequences
295 295 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
296 296 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
297 297
298 298 #self.SetEdgeMode(stc.STC_EDGE_LINE)
299 299 #self.SetEdgeColumn(80)
300 300
301 301 # styles
302 302 p = self.style
303 303 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
304 304 self.StyleClearAll()
305 305 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
306 306 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
307 307 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
308 308
309 309 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
310 310 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
311 311 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
312 312 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
313 313 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
314 314 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
315 315 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
316 316 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
317 317 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
318 318 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
319 319 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
320 320 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
321 321 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
322 322 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
323 323
324 324 def _on_key_down(self, event, skip=True):
325 325 """ Key press callback used for correcting behavior for
326 326 console-like interfaces: the cursor is constraint to be after
327 327 the last prompt.
328 328
329 329 Return True if event as been catched.
330 330 """
331 331 catched = True
332 332 # Intercept some specific keys.
333 333 if event.KeyCode == ord('L') and event.ControlDown() :
334 334 self.scroll_to_bottom()
335 335 elif event.KeyCode == ord('K') and event.ControlDown() :
336 336 self.input_buffer = ''
337 337 elif event.KeyCode == ord('A') and event.ControlDown() :
338 338 self.GotoPos(self.GetLength())
339 339 self.SetSelectionStart(self.current_prompt_pos)
340 340 self.SetSelectionEnd(self.GetCurrentPos())
341 341 catched = True
342 342 elif event.KeyCode == ord('E') and event.ControlDown() :
343 343 self.GotoPos(self.GetLength())
344 344 catched = True
345 345 elif event.KeyCode == wx.WXK_PAGEUP:
346 346 self.ScrollPages(-1)
347 347 elif event.KeyCode == wx.WXK_PAGEDOWN:
348 348 self.ScrollPages(1)
349 349 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
350 350 self.ScrollLines(-1)
351 351 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
352 352 self.ScrollLines(1)
353 353 else:
354 354 catched = False
355 355
356 356 if self.AutoCompActive():
357 357 event.Skip()
358 358 else:
359 359 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
360 360 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
361 361 catched = True
362 362 self.CallTipCancel()
363 363 self.write('\n', refresh=False)
364 364 # Under windows scintilla seems to be doing funny stuff to the
365 365 # line returns here, but the getter for input_buffer filters
366 366 # this out.
367 367 if sys.platform == 'win32':
368 368 self.input_buffer = self.input_buffer
369 369 self._on_enter()
370 370
371 371 elif event.KeyCode == wx.WXK_HOME:
372 372 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
373 373 self.GotoPos(self.current_prompt_pos)
374 374 catched = True
375 375
376 376 elif event.Modifiers == wx.MOD_SHIFT:
377 377 # FIXME: This behavior is not ideal: if the selection
378 378 # is already started, it will jump.
379 379 self.SetSelectionStart(self.current_prompt_pos)
380 380 self.SetSelectionEnd(self.GetCurrentPos())
381 381 catched = True
382 382
383 383 elif event.KeyCode == wx.WXK_UP:
384 384 if self.GetCurrentLine() > self.current_prompt_line:
385 385 if self.GetCurrentLine() == self.current_prompt_line + 1 \
386 386 and self.GetColumn(self.GetCurrentPos()) < \
387 387 self.GetColumn(self.current_prompt_pos):
388 388 self.GotoPos(self.current_prompt_pos)
389 389 else:
390 390 event.Skip()
391 391 catched = True
392 392
393 393 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
394 394 if self.GetCurrentPos() > self.current_prompt_pos:
395 395 event.Skip()
396 396 catched = True
397 397
398 398 if skip and not catched:
399 399 # Put the cursor back in the edit region
400 400 if self.GetCurrentPos() < self.current_prompt_pos:
401 401 self.GotoPos(self.current_prompt_pos)
402 402 else:
403 403 event.Skip()
404 404
405 405 return catched
406 406
407 407
408 408 def _on_key_up(self, event, skip=True):
409 409 """ If cursor is outside the editing region, put it back.
410 410 """
411 411 event.Skip()
412 412 if self.GetCurrentPos() < self.current_prompt_pos:
413 413 self.GotoPos(self.current_prompt_pos)
414 414
415 415
416 416
417 417 if __name__ == '__main__':
418 418 # Some simple code to test the console widget.
419 419 class MainWindow(wx.Frame):
420 420 def __init__(self, parent, id, title):
421 421 wx.Frame.__init__(self, parent, id, title, size=(300,250))
422 422 self._sizer = wx.BoxSizer(wx.VERTICAL)
423 423 self.console_widget = ConsoleWidget(self)
424 424 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
425 425 self.SetSizer(self._sizer)
426 426 self.SetAutoLayout(1)
427 427 self.Show(True)
428 428
429 429 app = wx.PySimpleApp()
430 430 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
431 431 w.SetSize((780, 460))
432 432 w.Show()
433 433
434 434 app.MainLoop()
435 435
436 436
General Comments 0
You need to be logged in to leave comments. Login now