##// END OF EJS Templates
Fix (hopefully) another threadign bug, reported by Jorgen.
fperez -
r283:6e9469cf
parent child
Show More
@@ -1,956 +1,960
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Shell classes.
3 3
4 4 All the matplotlib support code was co-developed with John Hunter,
5 5 matplotlib's author.
6 6
7 $Id: Shell.py 1297 2006-05-13 19:14:48Z fperez $"""
7 $Id: Shell.py 1313 2006-05-19 17:48:41Z fperez $"""
8 8
9 9 #*****************************************************************************
10 10 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #*****************************************************************************
15 15
16 16 from IPython import Release
17 17 __author__ = '%s <%s>' % Release.authors['Fernando']
18 18 __license__ = Release.license
19 19
20 20 # Code begins
21 21 import __builtin__
22 22 import __main__
23 23 import Queue
24 24 import os
25 25 import signal
26 26 import sys
27 27 import threading
28 28 import time
29 29
30 30 import IPython
31 31 from IPython import ultraTB
32 32 from IPython.genutils import Term,warn,error,flag_calls
33 33 from IPython.iplib import InteractiveShell
34 34 from IPython.ipmaker import make_IPython
35 35 from IPython.Magic import Magic
36 36 from IPython.ipstruct import Struct
37 37
38 38 # global flag to pass around information about Ctrl-C without exceptions
39 39 KBINT = False
40 40
41 41 # global flag to turn on/off Tk support.
42 42 USE_TK = False
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # This class is trivial now, but I want to have it in to publish a clean
46 46 # interface. Later when the internals are reorganized, code that uses this
47 47 # shouldn't have to change.
48 48
49 49 class IPShell:
50 50 """Create an IPython instance."""
51 51
52 52 def __init__(self,argv=None,user_ns=None,user_global_ns=None,
53 53 debug=1,shell_class=InteractiveShell):
54 54 self.IP = make_IPython(argv,user_ns=user_ns,user_global_ns=user_global_ns,
55 55 debug=debug,shell_class=shell_class)
56 56
57 57 def mainloop(self,sys_exit=0,banner=None):
58 58 self.IP.mainloop(banner)
59 59 if sys_exit:
60 60 sys.exit()
61 61
62 62 #-----------------------------------------------------------------------------
63 63 class IPShellEmbed:
64 64 """Allow embedding an IPython shell into a running program.
65 65
66 66 Instances of this class are callable, with the __call__ method being an
67 67 alias to the embed() method of an InteractiveShell instance.
68 68
69 69 Usage (see also the example-embed.py file for a running example):
70 70
71 71 ipshell = IPShellEmbed([argv,banner,exit_msg,rc_override])
72 72
73 73 - argv: list containing valid command-line options for IPython, as they
74 74 would appear in sys.argv[1:].
75 75
76 76 For example, the following command-line options:
77 77
78 78 $ ipython -prompt_in1 'Input <\\#>' -colors LightBG
79 79
80 80 would be passed in the argv list as:
81 81
82 82 ['-prompt_in1','Input <\\#>','-colors','LightBG']
83 83
84 84 - banner: string which gets printed every time the interpreter starts.
85 85
86 86 - exit_msg: string which gets printed every time the interpreter exits.
87 87
88 88 - rc_override: a dict or Struct of configuration options such as those
89 89 used by IPython. These options are read from your ~/.ipython/ipythonrc
90 90 file when the Shell object is created. Passing an explicit rc_override
91 91 dict with any options you want allows you to override those values at
92 92 creation time without having to modify the file. This way you can create
93 93 embeddable instances configured in any way you want without editing any
94 94 global files (thus keeping your interactive IPython configuration
95 95 unchanged).
96 96
97 97 Then the ipshell instance can be called anywhere inside your code:
98 98
99 99 ipshell(header='') -> Opens up an IPython shell.
100 100
101 101 - header: string printed by the IPython shell upon startup. This can let
102 102 you know where in your code you are when dropping into the shell. Note
103 103 that 'banner' gets prepended to all calls, so header is used for
104 104 location-specific information.
105 105
106 106 For more details, see the __call__ method below.
107 107
108 108 When the IPython shell is exited with Ctrl-D, normal program execution
109 109 resumes.
110 110
111 111 This functionality was inspired by a posting on comp.lang.python by cmkl
112 112 <cmkleffner@gmx.de> on Dec. 06/01 concerning similar uses of pyrepl, and
113 113 by the IDL stop/continue commands."""
114 114
115 115 def __init__(self,argv=None,banner='',exit_msg=None,rc_override=None):
116 116 """Note that argv here is a string, NOT a list."""
117 117 self.set_banner(banner)
118 118 self.set_exit_msg(exit_msg)
119 119 self.set_dummy_mode(0)
120 120
121 121 # sys.displayhook is a global, we need to save the user's original
122 122 # Don't rely on __displayhook__, as the user may have changed that.
123 123 self.sys_displayhook_ori = sys.displayhook
124 124
125 125 # save readline completer status
126 126 try:
127 127 #print 'Save completer',sys.ipcompleter # dbg
128 128 self.sys_ipcompleter_ori = sys.ipcompleter
129 129 except:
130 130 pass # not nested with IPython
131 131
132 132 # FIXME. Passing user_ns breaks namespace handling.
133 133 #self.IP = make_IPython(argv,user_ns=__main__.__dict__)
134 134 self.IP = make_IPython(argv,rc_override=rc_override,embedded=True)
135 135
136 136 # copy our own displayhook also
137 137 self.sys_displayhook_embed = sys.displayhook
138 138 # and leave the system's display hook clean
139 139 sys.displayhook = self.sys_displayhook_ori
140 140 # don't use the ipython crash handler so that user exceptions aren't
141 141 # trapped
142 142 sys.excepthook = ultraTB.FormattedTB(color_scheme = self.IP.rc.colors,
143 143 mode = self.IP.rc.xmode,
144 144 call_pdb = self.IP.rc.pdb)
145 145 self.restore_system_completer()
146 146
147 147 def restore_system_completer(self):
148 148 """Restores the readline completer which was in place.
149 149
150 150 This allows embedded IPython within IPython not to disrupt the
151 151 parent's completion.
152 152 """
153 153
154 154 try:
155 155 self.IP.readline.set_completer(self.sys_ipcompleter_ori)
156 156 sys.ipcompleter = self.sys_ipcompleter_ori
157 157 except:
158 158 pass
159 159
160 160 def __call__(self,header='',local_ns=None,global_ns=None,dummy=None):
161 161 """Activate the interactive interpreter.
162 162
163 163 __call__(self,header='',local_ns=None,global_ns,dummy=None) -> Start
164 164 the interpreter shell with the given local and global namespaces, and
165 165 optionally print a header string at startup.
166 166
167 167 The shell can be globally activated/deactivated using the
168 168 set/get_dummy_mode methods. This allows you to turn off a shell used
169 169 for debugging globally.
170 170
171 171 However, *each* time you call the shell you can override the current
172 172 state of dummy_mode with the optional keyword parameter 'dummy'. For
173 173 example, if you set dummy mode on with IPShell.set_dummy_mode(1), you
174 174 can still have a specific call work by making it as IPShell(dummy=0).
175 175
176 176 The optional keyword parameter dummy controls whether the call
177 177 actually does anything. """
178 178
179 179 # Allow the dummy parameter to override the global __dummy_mode
180 180 if dummy or (dummy != 0 and self.__dummy_mode):
181 181 return
182 182
183 183 # Set global subsystems (display,completions) to our values
184 184 sys.displayhook = self.sys_displayhook_embed
185 185 if self.IP.has_readline:
186 186 self.IP.readline.set_completer(self.IP.Completer.complete)
187 187
188 188 if self.banner and header:
189 189 format = '%s\n%s\n'
190 190 else:
191 191 format = '%s%s\n'
192 192 banner = format % (self.banner,header)
193 193
194 194 # Call the embedding code with a stack depth of 1 so it can skip over
195 195 # our call and get the original caller's namespaces.
196 196 self.IP.embed_mainloop(banner,local_ns,global_ns,stack_depth=1)
197 197
198 198 if self.exit_msg:
199 199 print self.exit_msg
200 200
201 201 # Restore global systems (display, completion)
202 202 sys.displayhook = self.sys_displayhook_ori
203 203 self.restore_system_completer()
204 204
205 205 def set_dummy_mode(self,dummy):
206 206 """Sets the embeddable shell's dummy mode parameter.
207 207
208 208 set_dummy_mode(dummy): dummy = 0 or 1.
209 209
210 210 This parameter is persistent and makes calls to the embeddable shell
211 211 silently return without performing any action. This allows you to
212 212 globally activate or deactivate a shell you're using with a single call.
213 213
214 214 If you need to manually"""
215 215
216 216 if dummy not in [0,1,False,True]:
217 217 raise ValueError,'dummy parameter must be boolean'
218 218 self.__dummy_mode = dummy
219 219
220 220 def get_dummy_mode(self):
221 221 """Return the current value of the dummy mode parameter.
222 222 """
223 223 return self.__dummy_mode
224 224
225 225 def set_banner(self,banner):
226 226 """Sets the global banner.
227 227
228 228 This banner gets prepended to every header printed when the shell
229 229 instance is called."""
230 230
231 231 self.banner = banner
232 232
233 233 def set_exit_msg(self,exit_msg):
234 234 """Sets the global exit_msg.
235 235
236 236 This exit message gets printed upon exiting every time the embedded
237 237 shell is called. It is None by default. """
238 238
239 239 self.exit_msg = exit_msg
240 240
241 241 #-----------------------------------------------------------------------------
242 242 def sigint_handler (signum,stack_frame):
243 243 """Sigint handler for threaded apps.
244 244
245 245 This is a horrible hack to pass information about SIGINT _without_ using
246 246 exceptions, since I haven't been able to properly manage cross-thread
247 247 exceptions in GTK/WX. In fact, I don't think it can be done (or at least
248 248 that's my understanding from a c.l.py thread where this was discussed)."""
249 249
250 250 global KBINT
251 251
252 252 print '\nKeyboardInterrupt - Press <Enter> to continue.',
253 253 Term.cout.flush()
254 254 # Set global flag so that runsource can know that Ctrl-C was hit
255 255 KBINT = True
256 256
257 257 class MTInteractiveShell(InteractiveShell):
258 258 """Simple multi-threaded shell."""
259 259
260 260 # Threading strategy taken from:
261 261 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109, by Brian
262 262 # McErlean and John Finlay. Modified with corrections by Antoon Pardon,
263 263 # from the pygtk mailing list, to avoid lockups with system calls.
264 264
265 265 # class attribute to indicate whether the class supports threads or not.
266 266 # Subclasses with thread support should override this as needed.
267 267 isthreaded = True
268 268
269 269 def __init__(self,name,usage=None,rc=Struct(opts=None,args=None),
270 270 user_ns=None,user_global_ns=None,banner2='',**kw):
271 271 """Similar to the normal InteractiveShell, but with threading control"""
272 272
273 273 InteractiveShell.__init__(self,name,usage,rc,user_ns,
274 274 user_global_ns,banner2)
275 275
276 276 # Locking control variable. We need to use a norma lock, not an RLock
277 277 # here. I'm not exactly sure why, it seems to me like it should be
278 278 # the opposite, but we deadlock with an RLock. Puzzled...
279 279 self.thread_ready = threading.Condition(threading.Lock())
280 280
281 281 # A queue to hold the code to be executed. A scalar variable is NOT
282 282 # enough, because uses like macros cause reentrancy.
283 283 self.code_queue = Queue.Queue()
284 284
285 285 # Stuff to do at closing time
286 286 self._kill = False
287 287 on_kill = kw.get('on_kill')
288 288 if on_kill is None:
289 289 on_kill = []
290 290 # Check that all things to kill are callable:
291 291 for t in on_kill:
292 292 if not callable(t):
293 293 raise TypeError,'on_kill must be a list of callables'
294 294 self.on_kill = on_kill
295 295
296 296 def runsource(self, source, filename="<input>", symbol="single"):
297 297 """Compile and run some source in the interpreter.
298 298
299 299 Modified version of code.py's runsource(), to handle threading issues.
300 300 See the original for full docstring details."""
301 301
302 302 global KBINT
303 303
304 304 # If Ctrl-C was typed, we reset the flag and return right away
305 305 if KBINT:
306 306 KBINT = False
307 307 return False
308 308
309 309 try:
310 310 code = self.compile(source, filename, symbol)
311 311 except (OverflowError, SyntaxError, ValueError):
312 312 # Case 1
313 313 self.showsyntaxerror(filename)
314 314 return False
315 315
316 316 if code is None:
317 317 # Case 2
318 318 return True
319 319
320 320 # Case 3
321 321 # Store code in queue, so the execution thread can handle it.
322 322
323 323 # Note that with macros and other applications, we MAY re-enter this
324 324 # section, so we have to acquire the lock with non-blocking semantics,
325 325 # else we deadlock.
326 326 got_lock = self.thread_ready.acquire(False)
327 327 self.code_queue.put(code)
328 328 if got_lock:
329 329 self.thread_ready.wait() # Wait until processed in timeout interval
330 330 self.thread_ready.release()
331 331
332 332 return False
333 333
334 334 def runcode(self):
335 335 """Execute a code object.
336 336
337 337 Multithreaded wrapper around IPython's runcode()."""
338 338
339 339 # lock thread-protected stuff
340 340 got_lock = self.thread_ready.acquire(False)
341 341
342 342 # Install sigint handler
343 343 try:
344 344 signal.signal(signal.SIGINT, sigint_handler)
345 345 except SystemError:
346 346 # This happens under Windows, which seems to have all sorts
347 347 # of problems with signal handling. Oh well...
348 348 pass
349 349
350 350 if self._kill:
351 351 print >>Term.cout, 'Closing threads...',
352 352 Term.cout.flush()
353 353 for tokill in self.on_kill:
354 354 tokill()
355 355 print >>Term.cout, 'Done.'
356 356
357 357 # Flush queue of pending code by calling the run methood of the parent
358 358 # class with all items which may be in the queue.
359 359 while 1:
360 360 try:
361 361 code_to_run = self.code_queue.get_nowait()
362 362 except Queue.Empty:
363 363 break
364 self.thread_ready.notify()
365 InteractiveShell.runcode(self,code_to_run)
364 if got_lock:
365 self.thread_ready.notify()
366 InteractiveShell.runcode(self,code_to_run)
367 else:
368 break
366 369
367 370 # We're done with thread-protected variables
368 371 if got_lock:
369 372 self.thread_ready.release()
370 373 # This MUST return true for gtk threading to work
371 374 return True
372 375
373 def kill (self):
376 def kill(self):
374 377 """Kill the thread, returning when it has been shut down."""
375 self.thread_ready.acquire(False)
378 got_lock = self.thread_ready.acquire(False)
376 379 self._kill = True
377 self.thread_ready.release()
380 if got_lock:
381 self.thread_ready.release()
378 382
379 383 class MatplotlibShellBase:
380 384 """Mixin class to provide the necessary modifications to regular IPython
381 385 shell classes for matplotlib support.
382 386
383 387 Given Python's MRO, this should be used as the FIRST class in the
384 388 inheritance hierarchy, so that it overrides the relevant methods."""
385 389
386 390 def _matplotlib_config(self,name):
387 391 """Return items needed to setup the user's shell with matplotlib"""
388 392
389 393 # Initialize matplotlib to interactive mode always
390 394 import matplotlib
391 395 from matplotlib import backends
392 396 matplotlib.interactive(True)
393 397
394 398 def use(arg):
395 399 """IPython wrapper for matplotlib's backend switcher.
396 400
397 401 In interactive use, we can not allow switching to a different
398 402 interactive backend, since thread conflicts will most likely crash
399 403 the python interpreter. This routine does a safety check first,
400 404 and refuses to perform a dangerous switch. It still allows
401 405 switching to non-interactive backends."""
402 406
403 407 if arg in backends.interactive_bk and arg != self.mpl_backend:
404 408 m=('invalid matplotlib backend switch.\n'
405 409 'This script attempted to switch to the interactive '
406 410 'backend: `%s`\n'
407 411 'Your current choice of interactive backend is: `%s`\n\n'
408 412 'Switching interactive matplotlib backends at runtime\n'
409 413 'would crash the python interpreter, '
410 414 'and IPython has blocked it.\n\n'
411 415 'You need to either change your choice of matplotlib backend\n'
412 416 'by editing your .matplotlibrc file, or run this script as a \n'
413 417 'standalone file from the command line, not using IPython.\n' %
414 418 (arg,self.mpl_backend) )
415 419 raise RuntimeError, m
416 420 else:
417 421 self.mpl_use(arg)
418 422 self.mpl_use._called = True
419 423
420 424 self.matplotlib = matplotlib
421 425 self.mpl_backend = matplotlib.rcParams['backend']
422 426
423 427 # we also need to block switching of interactive backends by use()
424 428 self.mpl_use = matplotlib.use
425 429 self.mpl_use._called = False
426 430 # overwrite the original matplotlib.use with our wrapper
427 431 matplotlib.use = use
428 432
429 433 # This must be imported last in the matplotlib series, after
430 434 # backend/interactivity choices have been made
431 435 try:
432 436 import matplotlib.pylab as pylab
433 437 self.pylab = pylab
434 438 self.pylab_name = 'pylab'
435 439 except ImportError:
436 440 import matplotlib.matlab as matlab
437 441 self.pylab = matlab
438 442 self.pylab_name = 'matlab'
439 443
440 444 self.pylab.show._needmain = False
441 445 # We need to detect at runtime whether show() is called by the user.
442 446 # For this, we wrap it into a decorator which adds a 'called' flag.
443 447 self.pylab.draw_if_interactive = flag_calls(self.pylab.draw_if_interactive)
444 448
445 449 # Build a user namespace initialized with matplotlib/matlab features.
446 450 user_ns = {'__name__':'__main__',
447 451 '__builtins__' : __builtin__ }
448 452
449 453 # Be careful not to remove the final \n in the code string below, or
450 454 # things will break badly with py22 (I think it's a python bug, 2.3 is
451 455 # OK).
452 456 pname = self.pylab_name # Python can't interpolate dotted var names
453 457 exec ("import matplotlib\n"
454 458 "import matplotlib.%(pname)s as %(pname)s\n"
455 459 "from matplotlib.%(pname)s import *\n" % locals()) in user_ns
456 460
457 461 # Build matplotlib info banner
458 462 b="""
459 463 Welcome to pylab, a matplotlib-based Python environment.
460 464 For more information, type 'help(pylab)'.
461 465 """
462 466 return user_ns,b
463 467
464 468 def mplot_exec(self,fname,*where,**kw):
465 469 """Execute a matplotlib script.
466 470
467 471 This is a call to execfile(), but wrapped in safeties to properly
468 472 handle interactive rendering and backend switching."""
469 473
470 474 #print '*** Matplotlib runner ***' # dbg
471 475 # turn off rendering until end of script
472 476 isInteractive = self.matplotlib.rcParams['interactive']
473 477 self.matplotlib.interactive(False)
474 478 self.safe_execfile(fname,*where,**kw)
475 479 self.matplotlib.interactive(isInteractive)
476 480 # make rendering call now, if the user tried to do it
477 481 if self.pylab.draw_if_interactive.called:
478 482 self.pylab.draw()
479 483 self.pylab.draw_if_interactive.called = False
480 484
481 485 # if a backend switch was performed, reverse it now
482 486 if self.mpl_use._called:
483 487 self.matplotlib.rcParams['backend'] = self.mpl_backend
484 488
485 489 def magic_run(self,parameter_s=''):
486 490 Magic.magic_run(self,parameter_s,runner=self.mplot_exec)
487 491
488 492 # Fix the docstring so users see the original as well
489 493 magic_run.__doc__ = "%s\n%s" % (Magic.magic_run.__doc__,
490 494 "\n *** Modified %run for Matplotlib,"
491 495 " with proper interactive handling ***")
492 496
493 497 # Now we provide 2 versions of a matplotlib-aware IPython base shells, single
494 498 # and multithreaded. Note that these are meant for internal use, the IPShell*
495 499 # classes below are the ones meant for public consumption.
496 500
497 501 class MatplotlibShell(MatplotlibShellBase,InteractiveShell):
498 502 """Single-threaded shell with matplotlib support."""
499 503
500 504 def __init__(self,name,usage=None,rc=Struct(opts=None,args=None),
501 505 user_ns=None,user_global_ns=None,**kw):
502 506 user_ns,b2 = self._matplotlib_config(name)
503 507 InteractiveShell.__init__(self,name,usage,rc,user_ns,user_global_ns,
504 508 banner2=b2,**kw)
505 509
506 510 class MatplotlibMTShell(MatplotlibShellBase,MTInteractiveShell):
507 511 """Multi-threaded shell with matplotlib support."""
508 512
509 513 def __init__(self,name,usage=None,rc=Struct(opts=None,args=None),
510 514 user_ns=None,user_global_ns=None, **kw):
511 515 user_ns,b2 = self._matplotlib_config(name)
512 516 MTInteractiveShell.__init__(self,name,usage,rc,user_ns,user_global_ns,
513 517 banner2=b2,**kw)
514 518
515 519 #-----------------------------------------------------------------------------
516 520 # Utility functions for the different GUI enabled IPShell* classes.
517 521
518 522 def get_tk():
519 523 """Tries to import Tkinter and returns a withdrawn Tkinter root
520 524 window. If Tkinter is already imported or not available, this
521 525 returns None. This function calls `hijack_tk` underneath.
522 526 """
523 527 if not USE_TK or sys.modules.has_key('Tkinter'):
524 528 return None
525 529 else:
526 530 try:
527 531 import Tkinter
528 532 except ImportError:
529 533 return None
530 534 else:
531 535 hijack_tk()
532 536 r = Tkinter.Tk()
533 537 r.withdraw()
534 538 return r
535 539
536 540 def hijack_tk():
537 541 """Modifies Tkinter's mainloop with a dummy so when a module calls
538 542 mainloop, it does not block.
539 543
540 544 """
541 545 def misc_mainloop(self, n=0):
542 546 pass
543 547 def tkinter_mainloop(n=0):
544 548 pass
545 549
546 550 import Tkinter
547 551 Tkinter.Misc.mainloop = misc_mainloop
548 552 Tkinter.mainloop = tkinter_mainloop
549 553
550 554 def update_tk(tk):
551 555 """Updates the Tkinter event loop. This is typically called from
552 556 the respective WX or GTK mainloops.
553 557 """
554 558 if tk:
555 559 tk.update()
556 560
557 561 def hijack_wx():
558 562 """Modifies wxPython's MainLoop with a dummy so user code does not
559 563 block IPython. The hijacked mainloop function is returned.
560 564 """
561 565 def dummy_mainloop(*args, **kw):
562 566 pass
563 567 import wxPython
564 568 ver = wxPython.__version__
565 569 orig_mainloop = None
566 570 if ver[:3] >= '2.5':
567 571 import wx
568 572 if hasattr(wx, '_core_'): core = getattr(wx, '_core_')
569 573 elif hasattr(wx, '_core'): core = getattr(wx, '_core')
570 574 else: raise AttributeError('Could not find wx core module')
571 575 orig_mainloop = core.PyApp_MainLoop
572 576 core.PyApp_MainLoop = dummy_mainloop
573 577 elif ver[:3] == '2.4':
574 578 orig_mainloop = wxPython.wxc.wxPyApp_MainLoop
575 579 wxPython.wxc.wxPyApp_MainLoop = dummy_mainloop
576 580 else:
577 581 warn("Unable to find either wxPython version 2.4 or >= 2.5.")
578 582 return orig_mainloop
579 583
580 584 def hijack_gtk():
581 585 """Modifies pyGTK's mainloop with a dummy so user code does not
582 586 block IPython. This function returns the original `gtk.mainloop`
583 587 function that has been hijacked.
584 588 """
585 589 def dummy_mainloop(*args, **kw):
586 590 pass
587 591 import gtk
588 592 if gtk.pygtk_version >= (2,4,0): orig_mainloop = gtk.main
589 593 else: orig_mainloop = gtk.mainloop
590 594 gtk.mainloop = dummy_mainloop
591 595 gtk.main = dummy_mainloop
592 596 return orig_mainloop
593 597
594 598 #-----------------------------------------------------------------------------
595 599 # The IPShell* classes below are the ones meant to be run by external code as
596 600 # IPython instances. Note that unless a specific threading strategy is
597 601 # desired, the factory function start() below should be used instead (it
598 602 # selects the proper threaded class).
599 603
600 604 class IPShellGTK(threading.Thread):
601 605 """Run a gtk mainloop() in a separate thread.
602 606
603 607 Python commands can be passed to the thread where they will be executed.
604 608 This is implemented by periodically checking for passed code using a
605 609 GTK timeout callback."""
606 610
607 611 TIMEOUT = 100 # Millisecond interval between timeouts.
608 612
609 613 def __init__(self,argv=None,user_ns=None,user_global_ns=None,
610 614 debug=1,shell_class=MTInteractiveShell):
611 615
612 616 import gtk
613 617
614 618 self.gtk = gtk
615 619 self.gtk_mainloop = hijack_gtk()
616 620
617 621 # Allows us to use both Tk and GTK.
618 622 self.tk = get_tk()
619 623
620 624 if gtk.pygtk_version >= (2,4,0): mainquit = self.gtk.main_quit
621 625 else: mainquit = self.gtk.mainquit
622 626
623 627 self.IP = make_IPython(argv,user_ns=user_ns,
624 628 user_global_ns=user_global_ns,
625 629 debug=debug,
626 630 shell_class=shell_class,
627 631 on_kill=[mainquit])
628 632
629 633 # HACK: slot for banner in self; it will be passed to the mainloop
630 634 # method only and .run() needs it. The actual value will be set by
631 635 # .mainloop().
632 636 self._banner = None
633 637
634 638 threading.Thread.__init__(self)
635 639
636 640 def run(self):
637 641 self.IP.mainloop(self._banner)
638 642 self.IP.kill()
639 643
640 644 def mainloop(self,sys_exit=0,banner=None):
641 645
642 646 self._banner = banner
643 647
644 648 if self.gtk.pygtk_version >= (2,4,0):
645 649 import gobject
646 650 gobject.idle_add(self.on_timer)
647 651 else:
648 652 self.gtk.idle_add(self.on_timer)
649 653
650 654 if sys.platform != 'win32':
651 655 try:
652 656 if self.gtk.gtk_version[0] >= 2:
653 657 self.gtk.threads_init()
654 658 except AttributeError:
655 659 pass
656 660 except RuntimeError:
657 661 error('Your pyGTK likely has not been compiled with '
658 662 'threading support.\n'
659 663 'The exception printout is below.\n'
660 664 'You can either rebuild pyGTK with threads, or '
661 665 'try using \n'
662 666 'matplotlib with a different backend (like Tk or WX).\n'
663 667 'Note that matplotlib will most likely not work in its '
664 668 'current state!')
665 669 self.IP.InteractiveTB()
666 670 self.start()
667 671 self.gtk.threads_enter()
668 672 self.gtk_mainloop()
669 673 self.gtk.threads_leave()
670 674 self.join()
671 675
672 676 def on_timer(self):
673 677 """Called when GTK is idle.
674 678
675 679 Must return True always, otherwise GTK stops calling it"""
676 680
677 681 update_tk(self.tk)
678 682 self.IP.runcode()
679 683 time.sleep(0.01)
680 684 return True
681 685
682 686 class IPShellWX(threading.Thread):
683 687 """Run a wx mainloop() in a separate thread.
684 688
685 689 Python commands can be passed to the thread where they will be executed.
686 690 This is implemented by periodically checking for passed code using a
687 691 GTK timeout callback."""
688 692
689 693 TIMEOUT = 100 # Millisecond interval between timeouts.
690 694
691 695 def __init__(self,argv=None,user_ns=None,user_global_ns=None,
692 696 debug=1,shell_class=MTInteractiveShell):
693 697
694 698 self.IP = make_IPython(argv,user_ns=user_ns,
695 699 user_global_ns=user_global_ns,
696 700 debug=debug,
697 701 shell_class=shell_class,
698 702 on_kill=[self.wxexit])
699 703
700 704 wantedwxversion=self.IP.rc.wxversion
701 705 if wantedwxversion!="0":
702 706 try:
703 707 import wxversion
704 708 except ImportError:
705 709 error('The wxversion module is needed for WX version selection')
706 710 else:
707 711 try:
708 712 wxversion.select(wantedwxversion)
709 713 except:
710 714 self.IP.InteractiveTB()
711 715 error('Requested wxPython version %s could not be loaded' %
712 716 wantedwxversion)
713 717
714 718 import wxPython.wx as wx
715 719
716 720 threading.Thread.__init__(self)
717 721 self.wx = wx
718 722 self.wx_mainloop = hijack_wx()
719 723
720 724 # Allows us to use both Tk and GTK.
721 725 self.tk = get_tk()
722 726
723 727
724 728 # HACK: slot for banner in self; it will be passed to the mainloop
725 729 # method only and .run() needs it. The actual value will be set by
726 730 # .mainloop().
727 731 self._banner = None
728 732
729 733 self.app = None
730 734
731 735 def wxexit(self, *args):
732 736 if self.app is not None:
733 737 self.app.agent.timer.Stop()
734 738 self.app.ExitMainLoop()
735 739
736 740 def run(self):
737 741 self.IP.mainloop(self._banner)
738 742 self.IP.kill()
739 743
740 744 def mainloop(self,sys_exit=0,banner=None):
741 745
742 746 self._banner = banner
743 747
744 748 self.start()
745 749
746 750 class TimerAgent(self.wx.wxMiniFrame):
747 751 wx = self.wx
748 752 IP = self.IP
749 753 tk = self.tk
750 754 def __init__(self, parent, interval):
751 755 style = self.wx.wxDEFAULT_FRAME_STYLE | self.wx.wxTINY_CAPTION_HORIZ
752 756 self.wx.wxMiniFrame.__init__(self, parent, -1, ' ', pos=(200, 200),
753 757 size=(100, 100),style=style)
754 758 self.Show(False)
755 759 self.interval = interval
756 760 self.timerId = self.wx.wxNewId()
757 761
758 762 def StartWork(self):
759 763 self.timer = self.wx.wxTimer(self, self.timerId)
760 764 self.wx.EVT_TIMER(self, self.timerId, self.OnTimer)
761 765 self.timer.Start(self.interval)
762 766
763 767 def OnTimer(self, event):
764 768 update_tk(self.tk)
765 769 self.IP.runcode()
766 770
767 771 class App(self.wx.wxApp):
768 772 wx = self.wx
769 773 TIMEOUT = self.TIMEOUT
770 774 def OnInit(self):
771 775 'Create the main window and insert the custom frame'
772 776 self.agent = TimerAgent(None, self.TIMEOUT)
773 777 self.agent.Show(self.wx.false)
774 778 self.agent.StartWork()
775 779 return self.wx.true
776 780
777 781 self.app = App(redirect=False)
778 782 self.wx_mainloop(self.app)
779 783 self.join()
780 784
781 785
782 786 class IPShellQt(threading.Thread):
783 787 """Run a Qt event loop in a separate thread.
784 788
785 789 Python commands can be passed to the thread where they will be executed.
786 790 This is implemented by periodically checking for passed code using a
787 791 Qt timer / slot."""
788 792
789 793 TIMEOUT = 100 # Millisecond interval between timeouts.
790 794
791 795 def __init__(self,argv=None,user_ns=None,user_global_ns=None,
792 796 debug=0,shell_class=MTInteractiveShell):
793 797
794 798 import qt
795 799
796 800 class newQApplication:
797 801 def __init__( self ):
798 802 self.QApplication = qt.QApplication
799 803
800 804 def __call__( *args, **kwargs ):
801 805 return qt.qApp
802 806
803 807 def exec_loop( *args, **kwargs ):
804 808 pass
805 809
806 810 def __getattr__( self, name ):
807 811 return getattr( self.QApplication, name )
808 812
809 813 qt.QApplication = newQApplication()
810 814
811 815 # Allows us to use both Tk and QT.
812 816 self.tk = get_tk()
813 817
814 818 self.IP = make_IPython(argv,user_ns=user_ns,
815 819 user_global_ns=user_global_ns,
816 820 debug=debug,
817 821 shell_class=shell_class,
818 822 on_kill=[qt.qApp.exit])
819 823
820 824 # HACK: slot for banner in self; it will be passed to the mainloop
821 825 # method only and .run() needs it. The actual value will be set by
822 826 # .mainloop().
823 827 self._banner = None
824 828
825 829 threading.Thread.__init__(self)
826 830
827 831 def run(self):
828 832 self.IP.mainloop(self._banner)
829 833 self.IP.kill()
830 834
831 835 def mainloop(self,sys_exit=0,banner=None):
832 836
833 837 import qt
834 838
835 839 self._banner = banner
836 840
837 841 if qt.QApplication.startingUp():
838 842 a = qt.QApplication.QApplication(sys.argv)
839 843 self.timer = qt.QTimer()
840 844 qt.QObject.connect( self.timer, qt.SIGNAL( 'timeout()' ), self.on_timer )
841 845
842 846 self.start()
843 847 self.timer.start( self.TIMEOUT, True )
844 848 while True:
845 849 if self.IP._kill: break
846 850 qt.qApp.exec_loop()
847 851 self.join()
848 852
849 853 def on_timer(self):
850 854 update_tk(self.tk)
851 855 result = self.IP.runcode()
852 856 self.timer.start( self.TIMEOUT, True )
853 857 return result
854 858
855 859 # A set of matplotlib public IPython shell classes, for single-threaded
856 860 # (Tk* and FLTK* backends) and multithreaded (GTK* and WX* backends) use.
857 861 class IPShellMatplotlib(IPShell):
858 862 """Subclass IPShell with MatplotlibShell as the internal shell.
859 863
860 864 Single-threaded class, meant for the Tk* and FLTK* backends.
861 865
862 866 Having this on a separate class simplifies the external driver code."""
863 867
864 868 def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1):
865 869 IPShell.__init__(self,argv,user_ns,user_global_ns,debug,
866 870 shell_class=MatplotlibShell)
867 871
868 872 class IPShellMatplotlibGTK(IPShellGTK):
869 873 """Subclass IPShellGTK with MatplotlibMTShell as the internal shell.
870 874
871 875 Multi-threaded class, meant for the GTK* backends."""
872 876
873 877 def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1):
874 878 IPShellGTK.__init__(self,argv,user_ns,user_global_ns,debug,
875 879 shell_class=MatplotlibMTShell)
876 880
877 881 class IPShellMatplotlibWX(IPShellWX):
878 882 """Subclass IPShellWX with MatplotlibMTShell as the internal shell.
879 883
880 884 Multi-threaded class, meant for the WX* backends."""
881 885
882 886 def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1):
883 887 IPShellWX.__init__(self,argv,user_ns,user_global_ns,debug,
884 888 shell_class=MatplotlibMTShell)
885 889
886 890 class IPShellMatplotlibQt(IPShellQt):
887 891 """Subclass IPShellQt with MatplotlibMTShell as the internal shell.
888 892
889 893 Multi-threaded class, meant for the Qt* backends."""
890 894
891 895 def __init__(self,argv=None,user_ns=None,user_global_ns=None,debug=1):
892 896 IPShellQt.__init__(self,argv,user_ns,user_global_ns,debug,
893 897 shell_class=MatplotlibMTShell)
894 898
895 899 #-----------------------------------------------------------------------------
896 900 # Factory functions to actually start the proper thread-aware shell
897 901
898 902 def _matplotlib_shell_class():
899 903 """Factory function to handle shell class selection for matplotlib.
900 904
901 905 The proper shell class to use depends on the matplotlib backend, since
902 906 each backend requires a different threading strategy."""
903 907
904 908 try:
905 909 import matplotlib
906 910 except ImportError:
907 911 error('matplotlib could NOT be imported! Starting normal IPython.')
908 912 sh_class = IPShell
909 913 else:
910 914 backend = matplotlib.rcParams['backend']
911 915 if backend.startswith('GTK'):
912 916 sh_class = IPShellMatplotlibGTK
913 917 elif backend.startswith('WX'):
914 918 sh_class = IPShellMatplotlibWX
915 919 elif backend.startswith('Qt'):