##// END OF EJS Templates
Bind Ctrl-C to kill process, when in process execution.
gvaroquaux -
Show More
@@ -0,0 +1,168 b''
1 # Addapted from killableprocess.py.
2 #______________________________________________________________________________
3 #
4 # killableprocess - subprocesses which can be reliably killed
5 #
6 # Parts of this module are copied from the subprocess.py file contained
7 # in the Python distribution.
8 #
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 #
11 # Additions and modifications written by Benjamin Smedberg
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 # <http://www.mozilla.org/>
14 #
15 # By obtaining, using, and/or copying this software and/or its
16 # associated documentation, you agree that you have read, understood,
17 # and will comply with the following terms and conditions:
18 #
19 # Permission to use, copy, modify, and distribute this software and
20 # its associated documentation for any purpose and without fee is
21 # hereby granted, provided that the above copyright notice appears in
22 # all copies, and that both that copyright notice and this permission
23 # notice appear in supporting documentation, and that the name of the
24 # author not be used in advertising or publicity pertaining to
25 # distribution of the software without specific, written prior
26 # permission.
27 #
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35
36 r"""killableprocess - Subprocesses which can be reliably killed
37
38 This module is a subclass of the builtin "subprocess" module. It allows
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40
41 It also adds a timeout argument to Wait() for a limited period of time before
42 forcefully killing the process.
43
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 """
48
49 import subprocess
50 from subprocess import PIPE
51 import sys
52 import os
53 import time
54 import types
55
56 try:
57 from subprocess import CalledProcessError
58 except ImportError:
59 # Python 2.4 doesn't implement CalledProcessError
60 class CalledProcessError(Exception):
61 """This exception is raised when a process run by check_call() returns
62 a non-zero exit status. The exit status will be stored in the
63 returncode attribute."""
64 def __init__(self, returncode, cmd):
65 self.returncode = returncode
66 self.cmd = cmd
67 def __str__(self):
68 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
69
70 mswindows = (sys.platform == "win32")
71
72 if mswindows:
73 import winprocess
74 else:
75 import signal
76
77 if not mswindows:
78 def DoNothing(*args):
79 pass
80
81 class Popen(subprocess.Popen):
82 if not mswindows:
83 # Override __init__ to set a preexec_fn
84 def __init__(self, *args, **kwargs):
85 if len(args) >= 7:
86 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
87
88 real_preexec_fn = kwargs.pop("preexec_fn", None)
89 def setpgid_preexec_fn():
90 os.setpgid(0, 0)
91 if real_preexec_fn:
92 apply(real_preexec_fn)
93
94 kwargs['preexec_fn'] = setpgid_preexec_fn
95
96 subprocess.Popen.__init__(self, *args, **kwargs)
97
98 if mswindows:
99 def _execute_child(self, args, executable, preexec_fn, close_fds,
100 cwd, env, universal_newlines, startupinfo,
101 creationflags, shell,
102 p2cread, p2cwrite,
103 c2pread, c2pwrite,
104 errread, errwrite):
105 if not isinstance(args, types.StringTypes):
106 args = subprocess.list2cmdline(args)
107
108 if startupinfo is None:
109 startupinfo = winprocess.STARTUPINFO()
110
111 if None not in (p2cread, c2pwrite, errwrite):
112 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
113
114 startupinfo.hStdInput = int(p2cread)
115 startupinfo.hStdOutput = int(c2pwrite)
116 startupinfo.hStdError = int(errwrite)
117 if shell:
118 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
119 startupinfo.wShowWindow = winprocess.SW_HIDE
120 comspec = os.environ.get("COMSPEC", "cmd.exe")
121 args = comspec + " /c " + args
122
123 # We create a new job for this process, so that we can kill
124 # the process and any sub-processes
125 self._job = winprocess.CreateJobObject()
126
127 creationflags |= winprocess.CREATE_SUSPENDED
128 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
129
130 hp, ht, pid, tid = winprocess.CreateProcess(
131 executable, args,
132 None, None, # No special security
133 1, # Must inherit handles!
134 creationflags,
135 winprocess.EnvironmentBlock(env),
136 cwd, startupinfo)
137
138 self._child_created = True
139 self._handle = hp
140 self._thread = ht
141 self.pid = pid
142
143 winprocess.AssignProcessToJobObject(self._job, hp)
144 winprocess.ResumeThread(ht)
145
146 if p2cread is not None:
147 p2cread.Close()
148 if c2pwrite is not None:
149 c2pwrite.Close()
150 if errwrite is not None:
151 errwrite.Close()
152
153 def kill(self, group=True):
154 """Kill the process. If group=True, all sub-processes will also be killed."""
155 if mswindows:
156 if group:
157 winprocess.TerminateJobObject(self._job, 127)
158 else:
159 winprocess.TerminateProcess(self._handle, 127)
160 self.returncode = 127
161 else:
162 if group:
163 os.killpg(self.pid, signal.SIGKILL)
164 else:
165 os.kill(self.pid, signal.SIGKILL)
166 self.returncode = -9
167
168
@@ -1,55 +1,55 b''
1 1 # encoding: utf-8
2 2 """
3 3 Object for encapsulating process execution by using callbacks for stdout,
4 4 stderr and stdin.
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 from subprocess import Popen, PIPE
18 from killable_process import Popen, PIPE
19 19 from threading import Thread
20 20 from time import sleep
21 21
22 22 class PipedProcess(Thread):
23 23
24 24 def __init__(self, command_string, out_callback,
25 25 end_callback=None,):
26 26 self.command_string = command_string
27 27 self.out_callback = out_callback
28 28 self.end_callback = end_callback
29 29 Thread.__init__(self)
30 30
31 31
32 32 def run(self):
33 33 """ Start the process and hook up the callbacks.
34 34 """
35 35 process = Popen((self.command_string + ' 2>&1', ), shell=True,
36 36 universal_newlines=True,
37 37 stdout=PIPE, stdin=PIPE)
38 38 self.process = process
39 39 while True:
40 40 out_char = process.stdout.read(1)
41 41 if out_char == '':
42 42 if process.poll() is not None:
43 43 # The process has finished
44 44 break
45 45 else:
46 46 # The process is not giving any interesting
47 47 # output. No use polling it immediatly.
48 48 sleep(0.1)
49 49 else:
50 50 self.out_callback(out_char)
51 51
52 52 if self.end_callback is not None:
53 53 self.end_callback()
54 54
55 55
@@ -1,355 +1,397 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 """
9 9
10 10 __docformat__ = "restructuredtext en"
11 11
12 12 #-------------------------------------------------------------------------------
13 13 # Copyright (C) 2008 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-------------------------------------------------------------------------------
18 18
19 19 #-------------------------------------------------------------------------------
20 20 # Imports
21 21 #-------------------------------------------------------------------------------
22 22
23 23
24 24 import wx
25 25 import re
26 26 from wx import stc
27 27 from console_widget import ConsoleWidget
28 28 import __builtin__
29 29 from time import sleep
30 30 import sys
31 import signal
31 32
32 33 from threading import Lock
33 34
34 35 from IPython.frontend.piped_process import PipedProcess
35 36 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
36 37
37 38 #_COMMAND_BG = '#FAFAF1' # Nice green
38 39 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
39 40 _ERROR_BG = '#FFF1F1' # Nice red
40 41
41 42 _RUNNING_BUFFER_MARKER = 31
42 43 _ERROR_MARKER = 30
43 44
44 45 #-------------------------------------------------------------------------------
45 46 # Classes to implement the Wx frontend
46 47 #-------------------------------------------------------------------------------
47 48 class WxController(PrefilterFrontEnd, ConsoleWidget):
48 49
49 50 output_prompt = \
50 51 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
51 52
53 # Print debug info on what is happening to the console.
52 54 debug = True
53 55
56 # The title of the terminal, as captured through the ANSI escape
57 # sequences.
58
59 def _set_title(self, title):
60 return self.Parent.SetTitle(title)
61
62 def _get_title(self):
63 return self.Parent.GetTitle()
64
65 title = property(_get_title, _set_title)
66
67 #--------------------------------------------------------------------------
68 # Private Attributes
69 #--------------------------------------------------------------------------
70
71 # A flag governing the behavior of the input. Can be:
72 #
73 # 'readline' for readline-like behavior with a prompt
74 # and an edit buffer.
75 # 'subprocess' for sending the raw input directly to a
76 # subprocess.
77 # 'buffering' for buffering of the input, that will be used
78 # when the input state switches back to another state.
79 _input_state = 'readline'
80
54 81 # Attribute to store reference to the pipes of a subprocess, if we
55 82 # are running any.
56 running_process = False
83 _running_process = False
57 84
58 85 # A queue for writing fast streams to the screen without flooding the
59 86 # event loop
60 write_buffer = []
87 _out_buffer = []
61 88
62 # A lock to lock the write_buffer to make sure we don't empty it
89 # A lock to lock the _out_buffer to make sure we don't empty it
63 90 # while it is being swapped
64 write_buffer_lock = Lock()
91 _out_buffer_lock = Lock()
65 92
66 93 #--------------------------------------------------------------------------
67 94 # Public API
68 95 #--------------------------------------------------------------------------
69 96
70 97 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
71 98 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
72 99 *args, **kwds):
73 100 """ Create Shell instance.
74 101 """
75 102 ConsoleWidget.__init__(self, parent, id, pos, size, style)
76 103 PrefilterFrontEnd.__init__(self)
77 104
78 105 # Marker for running buffer.
79 106 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
80 107 background=_RUNNING_BUFFER_BG)
81 108 # Marker for tracebacks.
82 109 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
83 110 background=_ERROR_BG)
84 111
85 112 # A time for flushing the write buffer
86 113 BUFFER_FLUSH_TIMER_ID = 100
87 114 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
88 115 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
89 116
90 117 def do_completion(self):
91 118 """ Do code completion.
92 119 """
93 120 if self.debug:
94 121 print >>sys.__stdout__, "do_completion",
95 122 line = self.get_current_edit_buffer()
96 123 new_line, completions = self.complete(line)
97 124 if len(completions)>1:
98 125 self.write_completion(completions)
99 126 self.replace_current_edit_buffer(new_line)
100 127 if self.debug:
101 128 print >>sys.__stdout__, completions
102 129
103 130
104 131 def do_calltip(self):
105 132 if self.debug:
106 133 print >>sys.__stdout__, "do_calltip"
107 134 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
108 135 symbol = self.get_current_edit_buffer()
109 136 symbol_string = separators.split(symbol)[-1]
110 137 base_symbol_string = symbol_string.split('.')[0]
111 138 if base_symbol_string in self.shell.user_ns:
112 139 symbol = self.shell.user_ns[base_symbol_string]
113 140 elif base_symbol_string in self.shell.user_global_ns:
114 141 symbol = self.shell.user_global_ns[base_symbol_string]
115 142 elif base_symbol_string in __builtin__.__dict__:
116 143 symbol = __builtin__.__dict__[base_symbol_string]
117 144 else:
118 145 return False
119 146 for name in symbol_string.split('.')[1:] + ['__doc__']:
120 147 symbol = getattr(symbol, name)
121 148 try:
122 149 self.AutoCompCancel()
123 150 wx.Yield()
124 151 self.CallTipShow(self.GetCurrentPos(), symbol)
125 152 except:
126 153 # The retrieve symbol couldn't be converted to a string
127 154 pass
128 155
129 156
130 157 def popup_completion(self, create=False):
131 158 """ Updates the popup completion menu if it exists. If create is
132 159 true, open the menu.
133 160 """
134 161 if self.debug:
135 162 print >>sys.__stdout__, "popup_completion",
136 163 line = self.get_current_edit_buffer()
137 164 if (self.AutoCompActive() and not line[-1] == '.') \
138 165 or create==True:
139 166 suggestion, completions = self.complete(line)
140 167 offset=0
141 168 if completions:
142 169 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
143 170 residual = complete_sep.split(line)[-1]
144 171 offset = len(residual)
145 172 self.pop_completion(completions, offset=offset)
146 173 if self.debug:
147 174 print >>sys.__stdout__, completions
148 175
149 176
177 def new_prompt(self, prompt):
178 self._input_state = 'readline'
179 ConsoleWidget.new_prompt(self, prompt)
180
181
150 182 def raw_input(self, prompt):
151 183 """ A replacement from python's raw_input.
152 184 """
153 185 self.new_prompt(prompt)
154 186 self.waiting = True
155 187 self.__old_on_enter = self._on_enter
156 188 def my_on_enter():
157 189 self.waiting = False
158 190 self._on_enter = my_on_enter
159 191 # XXX: Busy waiting, ugly.
160 192 while self.waiting:
161 193 wx.Yield()
162 194 sleep(0.1)
163 195 self._on_enter = self.__old_on_enter
196 self._input_state = 'buffering'
164 197 return self.get_current_edit_buffer().rstrip('\n')
165 198
166 199
167 200 def execute(self, python_string, raw_string=None):
201 self._input_state = 'buffering'
168 202 self.CallTipCancel()
169 203 self._cursor = wx.BusyCursor()
170 204 if raw_string is None:
171 205 raw_string = python_string
172 206 end_line = self.current_prompt_line \
173 207 + max(1, len(raw_string.split('\n'))-1)
174 208 for i in range(self.current_prompt_line, end_line):
175 209 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
176 210 # Update the display:
177 211 wx.Yield()
178 212 self.GotoPos(self.GetLength())
179 213 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
180 214
181 215
182 216 def capture_output(self):
183 217 self.__old_raw_input = __builtin__.raw_input
184 218 __builtin__.raw_input = self.raw_input
185 219 PrefilterFrontEnd.capture_output(self)
186 220
187 221
188 222 def release_output(self):
189 223 __builtin__.raw_input = self.__old_raw_input
190 224 PrefilterFrontEnd.capture_output(self)
191 225
192 226
193 227 def after_execute(self):
194 228 PrefilterFrontEnd.after_execute(self)
195 229 if hasattr(self, '_cursor'):
196 230 del self._cursor
197 231
198 232
199 233 def system_call(self, command_string):
200 self.running_process = True
201 self.running_process = PipedProcess(command_string,
234 self._input_state = 'subprocess'
235 self._running_process = PipedProcess(command_string,
202 236 out_callback=self.buffered_write,
203 237 end_callback = self._end_system_call)
204 self.running_process.start()
238 self._running_process.start()
205 239 # XXX: another one of these polling loops to have a blocking
206 240 # call
207 241 wx.Yield()
208 while self.running_process:
242 while self._running_process:
209 243 wx.Yield()
210 244 sleep(0.1)
211 245 # Be sure to flush the buffer.
212 246 self._buffer_flush(event=None)
213 247
214 248
215 249 def buffered_write(self, text):
216 250 """ A write method for streams, that caches the stream in order
217 251 to avoid flooding the event loop.
218 252
219 253 This can be called outside of the main loop, in separate
220 254 threads.
221 255 """
222 self.write_buffer_lock.acquire()
223 self.write_buffer.append(text)
224 self.write_buffer_lock.release()
256 self._out_buffer_lock.acquire()
257 self._out_buffer.append(text)
258 self._out_buffer_lock.release()
225 259 if not self._buffer_flush_timer.IsRunning():
226 260 self._buffer_flush_timer.Start(100) # milliseconds
227 261
228 262
229 def _end_system_call(self):
230 """ Called at the end of a system call.
231 """
232 self.running_process = False
233
234
235 def _buffer_flush(self, event):
236 """ Called by the timer to flush the write buffer.
237
238 This is always called in the mainloop, by the wx timer.
239 """
240 self.write_buffer_lock.acquire()
241 write_buffer = self.write_buffer
242 self.write_buffer = []
243 self.write_buffer_lock.release()
244 self.write(''.join(write_buffer))
245 self._buffer_flush_timer.Stop()
246
247
248 263 def show_traceback(self):
249 264 start_line = self.GetCurrentLine()
250 265 PrefilterFrontEnd.show_traceback(self)
251 266 wx.Yield()
252 267 for i in range(start_line, self.GetCurrentLine()):
253 268 self.MarkerAdd(i, _ERROR_MARKER)
254 269
255 270
256 271 #--------------------------------------------------------------------------
257 272 # Private API
258 273 #--------------------------------------------------------------------------
259 274
260 275 def _on_key_down(self, event, skip=True):
261 276 """ Capture the character events, let the parent
262 277 widget handle them, and put our logic afterward.
263 278 """
279 print >>sys.__stderr__, event.KeyCode
264 280 current_line_number = self.GetCurrentLine()
265 if self.running_process and event.KeyCode<256 \
281 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
282 # Capture Control-C
283 if self._input_state == 'subprocess':
284 if self.debug:
285 print >>sys.__stderr__, 'Killing running process'
286 self._running_process.process.kill()
287 elif self._input_state == 'buffering':
288 if self.debug:
289 print >>sys.__stderr__, 'Raising KeyboardException'
290 raise KeyboardException
291 # XXX: We need to make really sure we
292 # get back to a prompt.
293 elif self._input_state == 'subprocess' and event.KeyCode<256 \
266 294 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
267 # We are running a process, let us not be too clever.
295 # We are running a process, we redirect keys.
268 296 ConsoleWidget._on_key_down(self, event, skip=skip)
269 297 if self.debug:
270 298 print >>sys.__stderr__, chr(event.KeyCode)
271 self.running_process.process.stdin.write(chr(event.KeyCode))
299 self._running_process.process.stdin.write(chr(event.KeyCode))
272 300 elif event.KeyCode in (ord('('), 57):
273 301 # Calltips
274 302 event.Skip()
275 303 self.do_calltip()
276 304 elif self.AutoCompActive():
277 305 event.Skip()
278 306 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
279 307 wx.CallAfter(self.popup_completion, create=True)
280 308 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
281 309 wx.WXK_RIGHT):
282 310 wx.CallAfter(self.popup_completion)
283 311 else:
284 312 # Up history
285 313 if event.KeyCode == wx.WXK_UP and (
286 314 ( current_line_number == self.current_prompt_line and
287 315 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
288 316 or event.ControlDown() ):
289 317 new_buffer = self.get_history_previous(
290 318 self.get_current_edit_buffer())
291 319 if new_buffer is not None:
292 320 self.replace_current_edit_buffer(new_buffer)
293 321 if self.GetCurrentLine() > self.current_prompt_line:
294 322 # Go to first line, for seemless history up.
295 323 self.GotoPos(self.current_prompt_pos)
296 324 # Down history
297 325 elif event.KeyCode == wx.WXK_DOWN and (
298 326 ( current_line_number == self.LineCount -1 and
299 327 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
300 328 or event.ControlDown() ):
301 329 new_buffer = self.get_history_next()
302 330 if new_buffer is not None:
303 331 self.replace_current_edit_buffer(new_buffer)
304 332 # Tab-completion
305 333 elif event.KeyCode == ord('\t'):
306 334 last_line = self.get_current_edit_buffer().split('\n')[-1]
307 335 if not re.match(r'^\s*$', last_line):
308 336 self.do_completion()
309 337 else:
310 338 event.Skip()
311 339 else:
312 340 ConsoleWidget._on_key_down(self, event, skip=skip)
313 341
314 342
315 343 def _on_key_up(self, event, skip=True):
316 344 if event.KeyCode in (59, ord('.')):
317 345 # Intercepting '.'
318 346 event.Skip()
319 347 self.popup_completion(create=True)
320 348 else:
321 349 ConsoleWidget._on_key_up(self, event, skip=skip)
322 350
351
323 352 def _on_enter(self):
324 353 if self.debug:
325 354 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
326 355 PrefilterFrontEnd._on_enter(self)
327 356
328 def _set_title(self, title):
329 return self.Parent.SetTitle(title)
330 357
331 def _get_title(self):
332 return self.Parent.GetTitle()
358 def _end_system_call(self):
359 """ Called at the end of a system call.
360 """
361 print >>sys.__stderr__, 'End of system call'
362 self._input_state = 'buffering'
363 self._running_process = False
333 364
334 title = property(_get_title, _set_title)
365
366 def _buffer_flush(self, event):
367 """ Called by the timer to flush the write buffer.
368
369 This is always called in the mainloop, by the wx timer.
370 """
371 self._out_buffer_lock.acquire()
372 _out_buffer = self._out_buffer
373 self._out_buffer = []
374 self._out_buffer_lock.release()
375 self.write(''.join(_out_buffer))
376 self._buffer_flush_timer.Stop()
335 377
336 378
337 379 if __name__ == '__main__':
338 380 class MainWindow(wx.Frame):
339 381 def __init__(self, parent, id, title):
340 382 wx.Frame.__init__(self, parent, id, title, size=(300,250))
341 383 self._sizer = wx.BoxSizer(wx.VERTICAL)
342 384 self.shell = WxController(self)
343 385 self._sizer.Add(self.shell, 1, wx.EXPAND)
344 386 self.SetSizer(self._sizer)
345 387 self.SetAutoLayout(1)
346 388 self.Show(True)
347 389
348 390 app = wx.PySimpleApp()
349 391 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
350 392 frame.shell.SetFocus()
351 393 frame.SetSize((680, 460))
352 394 self = frame.shell
353 395
354 396 app.MainLoop()
355 397
General Comments 0
You need to be logged in to leave comments. Login now