##// END OF EJS Templates
Tweak magics.
Gael Varoquaux -
Show More
@@ -1,149 +1,159 b''
1 1 """
2 2 Frontend class that uses IPython0 to prefilter the inputs.
3 3
4 4 Using the IPython0 mechanism gives us access to the magics.
5 5 """
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 #-------------------------------------------------------------------------------
16 16 # Imports
17 17 #-------------------------------------------------------------------------------
18 18 import sys
19 19
20 20 from linefrontendbase import LineFrontEndBase, common_prefix
21 21
22 22 from IPython.ipmaker import make_IPython
23 23 from IPython.ipapi import IPApi
24 24 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
25 25
26 26 from IPython.genutils import Term
27 27 import pydoc
28 28
29 29 #-------------------------------------------------------------------------------
30 30 # Utility functions (temporary, should be moved out of here)
31 31 #-------------------------------------------------------------------------------
32 32 import os
33 33 def xterm_system(command):
34 34 """ Run a command in a separate console window.
35 35 """
36 36 os.system(("""xterm -title "%s" -e \'/bin/sh -c "%s ; """
37 37 """echo; echo press enter to close ; """
38 38 # """echo \\"\x1b]0;%s (finished -- press enter to close)\x07\\" ;
39 39 """read foo;"\' """) % (command, command) )
40 40
41 def system_call(command):
42 """ Temporary hack for aliases
43 """
44 def my_system_call(args):
45 os.system("%s %s" % (command, args))
46 return my_system_call
47
41 48 #-------------------------------------------------------------------------------
42 49 # Frontend class using ipython0 to do the prefiltering.
43 50 #-------------------------------------------------------------------------------
44 51 class PrefilterFrontEnd(LineFrontEndBase):
45 52
46 53 def __init__(self, *args, **kwargs):
47 54 LineFrontEndBase.__init__(self, *args, **kwargs)
48 55 # Instanciate an IPython0 interpreter to be able to use the
49 56 # prefiltering.
50 57 self.ipython0 = make_IPython()
51 58 # Set the pager:
52 59 self.ipython0.set_hook('show_in_pager',
53 60 lambda s, string: self.write("\n"+string))
54 61 self.ipython0.write = self.write
55 62 self._ip = _ip = IPApi(self.ipython0)
56 63 # XXX: Hack: mix the two namespaces
57 64 self.shell.user_ns = self.ipython0.user_ns
58 65 self.shell.user_global_ns = self.ipython0.user_global_ns
59 66 # Make sure the raw system call doesn't get called, as we don't
60 67 # have a stdin accessible.
61 68 self._ip.system = xterm_system
69 # XXX: Muck around with magics so that they work better
70 # in our environment
71 self.ipython0.magic_ls = system_call('ls -CF')
62 72 self.shell.output_trap = RedirectorOutputTrap(
63 73 out_callback=self.write,
64 74 err_callback=self.write,
65 75 )
66 76 # Capture and release the outputs, to make sure all the
67 77 # shadow variables are set
68 78 self.capture_output()
69 79 self.release_output()
70 80
71 81
72 82 def prefilter_input(self, input_string):
73 83 """ Using IPython0 to prefilter the commands.
74 84 """
75 85 input_string = LineFrontEndBase.prefilter_input(self, input_string)
76 86 filtered_lines = []
77 87 # The IPython0 prefilters sometime produce output. We need to
78 88 # capture it.
79 89 self.capture_output()
80 90 self.last_result = dict(number=self.prompt_number)
81 91 try:
82 92 for line in input_string.split('\n'):
83 93 filtered_lines.append(self.ipython0.prefilter(line, False))
84 94 except:
85 95 # XXX: probably not the right thing to do.
86 96 self.ipython0.showsyntaxerror()
87 97 self.after_execute()
88 98 finally:
89 99 self.release_output()
90 100
91 101 filtered_string = '\n'.join(filtered_lines)
92 102 return filtered_string
93 103
94 104
95 105 def show_traceback(self):
96 106 self.capture_output()
97 107 self.ipython0.showtraceback()
98 108 self.release_output()
99 109
100 110
101 111 def execute(self, python_string, raw_string=None):
102 112 self.capture_output()
103 113 LineFrontEndBase.execute(self, python_string,
104 114 raw_string=raw_string)
105 115 self.release_output()
106 116
107 117
108 118 def capture_output(self):
109 119 """ Capture all the output mechanisms we can think of.
110 120 """
111 121 self.__old_cout_write = Term.cout.write
112 122 self.__old_err_write = Term.cerr.write
113 123 Term.cout.write = self.write
114 124 Term.cerr.write = self.write
115 125 self.__old_stdout = sys.stdout
116 126 self.__old_stderr= sys.stderr
117 127 sys.stdout = Term.cout
118 128 sys.stderr = Term.cerr
119 129 self.__old_help_output = pydoc.help.output
120 130 pydoc.help.output = self.shell.output_trap.out
121 131
122 132
123 133 def release_output(self):
124 134 """ Release all the different captures we have made.
125 135 """
126 136 Term.cout.write = self.__old_cout_write
127 137 Term.cerr.write = self.__old_err_write
128 138 sys.stdout = self.__old_stdout
129 139 sys.stderr = self.__old_stderr
130 140 pydoc.help.output = self.__old_help_output
131 141
132 142
133 143 def complete(self, line):
134 144 word = line.split('\n')[-1].split(' ')[-1]
135 145 completions = self.ipython0.complete(word)
136 146 # FIXME: The proper sort should be done in the complete method.
137 147 key = lambda x: x.replace('_', '')
138 148 completions.sort(key=key)
139 149 if completions:
140 150 prefix = common_prefix(completions)
141 151 line = line[:-len(word)] + prefix
142 152 return line, completions
143 153
144 154
145 155 def do_exit(self):
146 156 """ Exit the shell, cleanup and save the history.
147 157 """
148 158 self.ipython0.atexit_operations()
149 159
@@ -1,423 +1,421 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
27 27 import re
28 28
29 29 # FIXME: Need to provide an API for non user-generated display on the
30 30 # screen: this should not be editable by the user.
31 31
32 32 if wx.Platform == '__WXMSW__':
33 33 _DEFAULT_SIZE = 80
34 34 else:
35 35 _DEFAULT_SIZE = 10
36 36
37 37 _DEFAULT_STYLE = {
38 38 'stdout' : 'fore:#0000FF',
39 39 'stderr' : 'fore:#007f00',
40 40 'trace' : 'fore:#FF0000',
41 41
42 42 'default' : 'size:%d' % _DEFAULT_SIZE,
43 43 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 44 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45 45
46 46 # properties for the various Python lexer styles
47 47 'comment' : 'fore:#007F00',
48 48 'number' : 'fore:#007F7F',
49 49 'string' : 'fore:#7F007F,italic',
50 50 'char' : 'fore:#7F007F,italic',
51 51 'keyword' : 'fore:#00007F,bold',
52 52 'triple' : 'fore:#7F0000',
53 53 'tripledouble' : 'fore:#7F0000',
54 54 'class' : 'fore:#0000FF,bold,underline',
55 55 'def' : 'fore:#007F7F,bold',
56 56 'operator' : 'bold'
57 57 }
58 58
59 59 # new style numbers
60 60 _STDOUT_STYLE = 15
61 61 _STDERR_STYLE = 16
62 62 _TRACE_STYLE = 17
63 63
64 64
65 65 # system colors
66 66 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
67 67
68 68 #-------------------------------------------------------------------------------
69 69 # The console widget class
70 70 #-------------------------------------------------------------------------------
71 71 class ConsoleWidget(editwindow.EditWindow):
72 72 """ Specialized styled text control view for console-like workflow.
73 73
74 74 This widget is mainly interested in dealing with the prompt and
75 75 keeping the cursor inside the editing line.
76 76 """
77 77
78 78 title = 'Console'
79 79
80 80 style = _DEFAULT_STYLE.copy()
81 81
82 82 # Translation table from ANSI escape sequences to color. Override
83 83 # this to specify your colors.
84 84 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
85 85 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
86 86 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
87 87 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
88 88 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
89 89 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
90 90 '1;34': [12, 'LIGHT BLUE'], '1;35':
91 91 [13, 'MEDIUM VIOLET RED'],
92 92 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
93 93
94 94 # The color of the carret (call _apply_style() after setting)
95 95 carret_color = 'BLACK'
96 96
97 97
98 98 #--------------------------------------------------------------------------
99 99 # Public API
100 100 #--------------------------------------------------------------------------
101 101
102 102 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
103 103 size=wx.DefaultSize, style=0, ):
104 104 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
105 105 self.configure_scintilla()
106 106
107 107 # FIXME: we need to retrieve this from the interpreter.
108 108 self.prompt = \
109 109 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
110 110 self.new_prompt(self.prompt % 1)
111 111
112 112 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
113 113 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
114 114
115 115
116 116 def configure_scintilla(self):
117 117 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
118 118 # the widget
119 119 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
120 120 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
121 121 # Also allow Ctrl Shift "=" for poor non US keyboard users.
122 122 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
123 123 stc.STC_CMD_ZOOMIN)
124 124
125 125 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
126 126 # stc.STC_CMD_PAGEUP)
127 127
128 128 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
129 129 # stc.STC_CMD_PAGEDOWN)
130 130
131 131 # Keys: we need to clear some of the keys the that don't play
132 132 # well with a console.
133 133 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
134 134 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
135 135 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
136 136
137 137
138 138 self.SetEOLMode(stc.STC_EOL_CRLF)
139 139 self.SetWrapMode(stc.STC_WRAP_CHAR)
140 140 self.SetWrapMode(stc.STC_WRAP_WORD)
141 141 self.SetBufferedDraw(True)
142 142 self.SetUseAntiAliasing(True)
143 143 self.SetLayoutCache(stc.STC_CACHE_PAGE)
144 144 self.SetUndoCollection(False)
145 145 self.SetUseTabs(True)
146 146 self.SetIndent(4)
147 147 self.SetTabWidth(4)
148 148
149 149 self.EnsureCaretVisible()
150 150 # we don't want scintilla's autocompletion to choose
151 151 # automaticaly out of a single choice list, as we pop it up
152 152 # automaticaly
153 153 self.AutoCompSetChooseSingle(False)
154 154 self.AutoCompSetMaxHeight(10)
155 155
156 156 self.SetMargins(3, 3) #text is moved away from border with 3px
157 157 # Suppressing Scintilla margins
158 158 self.SetMarginWidth(0, 0)
159 159 self.SetMarginWidth(1, 0)
160 160 self.SetMarginWidth(2, 0)
161 161
162 162 self._apply_style()
163 163
164 164 # Xterm escape sequences
165 165 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
166 166 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
167 167
168 168 #self.SetEdgeMode(stc.STC_EDGE_LINE)
169 169 #self.SetEdgeColumn(80)
170 170
171 171 # styles
172 172 p = self.style
173 173 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
174 174 self.StyleClearAll()
175 175 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
176 176 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
177 177 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
178 178
179 179 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
180 180 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
181 181 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
182 182 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
183 183 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
184 184 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
185 185 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
186 186 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
187 187 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
188 188 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
189 189 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
190 190 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
191 191 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
192 192 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
193 193
194 194
195 195 def write(self, text):
196 196 """ Write given text to buffer, while translating the ansi escape
197 197 sequences.
198 198 """
199 199 # XXX: do not put print statements in this method, the print
200 200 # statements will call this method, and you will end up with
201 201 # an infinit loop
202 202 title = self.title_pat.split(text)
203 203 if len(title)>1:
204 import sys
205 print >>sys.__stderr__, "title :", title
206 204 self.title = title[-2]
207 205
208 206 text = self.title_pat.sub('', text)
209 207 segments = self.color_pat.split(text)
210 208 segment = segments.pop(0)
211 209 self.StartStyling(self.GetLength(), 0xFF)
212 210 self.AppendText(segment)
213 211
214 212 if segments:
215 213 for ansi_tag, text in zip(segments[::2], segments[1::2]):
216 214 self.StartStyling(self.GetLength(), 0xFF)
217 215 self.AppendText(text)
218 216
219 if ansi_tag == '0':
217 if ansi_tag not in self.ANSI_STYLES:
220 218 style = 0
221 219 else:
222 220 style = self.ANSI_STYLES[ansi_tag][0]
223 221
224 222 self.SetStyling(len(text), style)
225 223
226 224 self.GotoPos(self.GetLength())
227 225 wx.Yield()
228 226
229 227
230 228 def new_prompt(self, prompt):
231 229 """ Prints a prompt at start of line, and move the start of the
232 230 current block there.
233 231
234 232 The prompt can be give with ascii escape sequences.
235 233 """
236 234 self.write(prompt)
237 235 # now we update our cursor giving end of prompt
238 236 self.current_prompt_pos = self.GetLength()
239 237 self.current_prompt_line = self.GetCurrentLine()
240 238 wx.Yield()
241 239 self.EnsureCaretVisible()
242 240
243 241
244 242 def replace_current_edit_buffer(self, text):
245 243 """ Replace currently entered command line with given text.
246 244 """
247 245 self.SetSelection(self.current_prompt_pos, self.GetLength())
248 246 self.ReplaceSelection(text)
249 247 self.GotoPos(self.GetLength())
250 248
251 249
252 250 def get_current_edit_buffer(self):
253 251 """ Returns the text in current edit buffer.
254 252 """
255 253 return self.GetTextRange(self.current_prompt_pos,
256 254 self.GetLength())
257 255
258 256
259 257 #--------------------------------------------------------------------------
260 258 # Private API
261 259 #--------------------------------------------------------------------------
262 260
263 261 def _apply_style(self):
264 262 """ Applies the colors for the different text elements and the
265 263 carret.
266 264 """
267 265 self.SetCaretForeground(self.carret_color)
268 266
269 267 #self.StyleClearAll()
270 268 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
271 269 "fore:#FF0000,back:#0000FF,bold")
272 270 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
273 271 "fore:#000000,back:#FF0000,bold")
274 272
275 273 for style in self.ANSI_STYLES.values():
276 274 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
277 275
278 276
279 277 def write_completion(self, possibilities):
280 278 # FIXME: This is non Wx specific and needs to be moved into
281 279 # the base class.
282 280 current_buffer = self.get_current_edit_buffer()
283 281
284 282 self.write('\n')
285 283 max_len = len(max(possibilities, key=len)) + 1
286 284
287 285 #now we check how much symbol we can put on a line...
288 286 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
289 287 symbols_per_line = max(1, chars_per_line/max_len)
290 288
291 289 pos = 1
292 290 buf = []
293 291 for symbol in possibilities:
294 292 if pos < symbols_per_line:
295 293 buf.append(symbol.ljust(max_len))
296 294 pos += 1
297 295 else:
298 296 buf.append(symbol.rstrip() +'\n')
299 297 pos = 1
300 298 self.write(''.join(buf))
301 299 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
302 300 self.replace_current_edit_buffer(current_buffer)
303 301
304 302
305 303 def pop_completion(self, possibilities, offset=0):
306 304 """ Pops up an autocompletion menu. Offset is the offset
307 305 in characters of the position at which the menu should
308 306 appear, relativ to the cursor.
309 307 """
310 308 self.AutoCompSetIgnoreCase(False)
311 309 self.AutoCompSetAutoHide(False)
312 310 self.AutoCompSetMaxHeight(len(possibilities))
313 311 self.AutoCompShow(offset, " ".join(possibilities))
314 312
315 313
316 314 def scroll_to_bottom(self):
317 315 maxrange = self.GetScrollRange(wx.VERTICAL)
318 316 self.ScrollLines(maxrange)
319 317
320 318
321 319 def _on_enter(self):
322 320 """ Called when the return key is hit.
323 321 """
324 322 pass
325 323
326 324
327 325 def _on_key_down(self, event, skip=True):
328 326 """ Key press callback used for correcting behavior for
329 327 console-like interfaces: the cursor is constraint to be after
330 328 the last prompt.
331 329
332 330 Return True if event as been catched.
333 331 """
334 332 catched = True
335 333 # Intercept some specific keys.
336 334 if event.KeyCode == ord('L') and event.ControlDown() :
337 335 self.scroll_to_bottom()
338 336 elif event.KeyCode == ord('K') and event.ControlDown() :
339 337 self.replace_current_edit_buffer('')
340 338 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
341 339 self.ScrollPages(-1)
342 340 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
343 341 self.ScrollPages(1)
344 342 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
345 343 self.ScrollLines(-1)
346 344 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
347 345 self.ScrollLines(1)
348 346 else:
349 347 catched = False
350 348
351 349 if self.AutoCompActive():
352 350 event.Skip()
353 351 else:
354 352 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
355 353 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
356 354 catched = True
357 355 self.CallTipCancel()
358 356 self.write('\n')
359 357 self._on_enter()
360 358
361 359 elif event.KeyCode == wx.WXK_HOME:
362 360 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
363 361 self.GotoPos(self.current_prompt_pos)
364 362 catched = True
365 363
366 364 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
367 365 # FIXME: This behavior is not ideal: if the selection
368 366 # is already started, it will jump.
369 367 self.SetSelectionStart(self.current_prompt_pos)
370 368 self.SetSelectionEnd(self.GetCurrentPos())
371 369 catched = True
372 370
373 371 elif event.KeyCode == wx.WXK_UP:
374 372 if self.GetCurrentLine() > self.current_prompt_line:
375 373 if self.GetCurrentLine() == self.current_prompt_line + 1 \
376 374 and self.GetColumn(self.GetCurrentPos()) < \
377 375 self.GetColumn(self.current_prompt_pos):
378 376 self.GotoPos(self.current_prompt_pos)
379 377 else:
380 378 event.Skip()
381 379 catched = True
382 380
383 381 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
384 382 if self.GetCurrentPos() > self.current_prompt_pos:
385 383 event.Skip()
386 384 catched = True
387 385
388 386 if skip and not catched:
389 387 event.Skip()
390 388
391 389 return catched
392 390
393 391
394 392 def _on_key_up(self, event, skip=True):
395 393 """ If cursor is outside the editing region, put it back.
396 394 """
397 395 event.Skip()
398 396 if self.GetCurrentPos() < self.current_prompt_pos:
399 397 self.GotoPos(self.current_prompt_pos)
400 398
401 399
402 400
403 401
404 402 if __name__ == '__main__':
405 403 # Some simple code to test the console widget.
406 404 class MainWindow(wx.Frame):
407 405 def __init__(self, parent, id, title):
408 406 wx.Frame.__init__(self, parent, id, title, size=(300,250))
409 407 self._sizer = wx.BoxSizer(wx.VERTICAL)
410 408 self.console_widget = ConsoleWidget(self)
411 409 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
412 410 self.SetSizer(self._sizer)
413 411 self.SetAutoLayout(1)
414 412 self.Show(True)
415 413
416 414 app = wx.PySimpleApp()
417 415 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
418 416 w.SetSize((780, 460))
419 417 w.Show()
420 418
421 419 app.MainLoop()
422 420
423 421
General Comments 0
You need to be logged in to leave comments. Login now