##// END OF EJS Templates
Fix tests for IPython.lib
Thomas Kluyver -
Show More
@@ -1,529 +1,532 b''
1 1 # coding: utf-8
2 2 """
3 3 Inputhook management for GUI event loop integration.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 try:
18 18 import ctypes
19 19 except ImportError:
20 20 ctypes = None
21 21 import os
22 22 import sys
23 23 from distutils.version import LooseVersion as V
24 24
25 25 from IPython.utils.warn import warn
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Constants
29 29 #-----------------------------------------------------------------------------
30 30
31 31 # Constants for identifying the GUI toolkits.
32 32 GUI_WX = 'wx'
33 33 GUI_QT = 'qt'
34 34 GUI_QT4 = 'qt4'
35 35 GUI_GTK = 'gtk'
36 36 GUI_TK = 'tk'
37 37 GUI_OSX = 'osx'
38 38 GUI_GLUT = 'glut'
39 39 GUI_PYGLET = 'pyglet'
40 40 GUI_GTK3 = 'gtk3'
41 41 GUI_NONE = 'none' # i.e. disable
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Utilities
45 45 #-----------------------------------------------------------------------------
46 46
47 47 def _stdin_ready_posix():
48 48 """Return True if there's something to read on stdin (posix version)."""
49 49 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
50 50 return bool(infds)
51 51
52 52 def _stdin_ready_nt():
53 53 """Return True if there's something to read on stdin (nt version)."""
54 54 return msvcrt.kbhit()
55 55
56 56 def _stdin_ready_other():
57 57 """Return True, assuming there's something to read on stdin."""
58 58 return True #
59 59
60 60
61 61 def _ignore_CTRL_C_posix():
62 62 """Ignore CTRL+C (SIGINT)."""
63 63 signal.signal(signal.SIGINT, signal.SIG_IGN)
64 64
65 65 def _allow_CTRL_C_posix():
66 66 """Take CTRL+C into account (SIGINT)."""
67 67 signal.signal(signal.SIGINT, signal.default_int_handler)
68 68
69 69 def _ignore_CTRL_C_other():
70 70 """Ignore CTRL+C (not implemented)."""
71 71 pass
72 72
73 73 def _allow_CTRL_C_other():
74 74 """Take CTRL+C into account (not implemented)."""
75 75 pass
76 76
77 77 if os.name == 'posix':
78 78 import select
79 79 import signal
80 80 stdin_ready = _stdin_ready_posix
81 81 ignore_CTRL_C = _ignore_CTRL_C_posix
82 82 allow_CTRL_C = _allow_CTRL_C_posix
83 83 elif os.name == 'nt':
84 84 import msvcrt
85 85 stdin_ready = _stdin_ready_nt
86 86 ignore_CTRL_C = _ignore_CTRL_C_other
87 87 allow_CTRL_C = _allow_CTRL_C_other
88 88 else:
89 89 stdin_ready = _stdin_ready_other
90 90 ignore_CTRL_C = _ignore_CTRL_C_other
91 91 allow_CTRL_C = _allow_CTRL_C_other
92 92
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Main InputHookManager class
96 96 #-----------------------------------------------------------------------------
97 97
98 98
99 99 class InputHookManager(object):
100 100 """Manage PyOS_InputHook for different GUI toolkits.
101 101
102 102 This class installs various hooks under ``PyOSInputHook`` to handle
103 103 GUI event loop integration.
104 104 """
105 105
106 106 def __init__(self):
107 107 if ctypes is None:
108 108 warn("IPython GUI event loop requires ctypes, %gui will not be available")
109 109 return
110 110 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
111 111 self._apps = {}
112 112 self._reset()
113 113
114 114 def _reset(self):
115 115 self._callback_pyfunctype = None
116 116 self._callback = None
117 117 self._installed = False
118 118 self._current_gui = None
119 119
120 120 def get_pyos_inputhook(self):
121 121 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
122 122 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
123 123
124 124 def get_pyos_inputhook_as_func(self):
125 125 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
126 126 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
127 127
128 128 def set_inputhook(self, callback):
129 129 """Set PyOS_InputHook to callback and return the previous one."""
130 130 # On platforms with 'readline' support, it's all too likely to
131 131 # have a KeyboardInterrupt signal delivered *even before* an
132 132 # initial ``try:`` clause in the callback can be executed, so
133 133 # we need to disable CTRL+C in this situation.
134 134 ignore_CTRL_C()
135 135 self._callback = callback
136 136 self._callback_pyfunctype = self.PYFUNC(callback)
137 137 pyos_inputhook_ptr = self.get_pyos_inputhook()
138 138 original = self.get_pyos_inputhook_as_func()
139 139 pyos_inputhook_ptr.value = \
140 140 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
141 141 self._installed = True
142 142 return original
143 143
144 144 def clear_inputhook(self, app=None):
145 145 """Set PyOS_InputHook to NULL and return the previous one.
146 146
147 147 Parameters
148 148 ----------
149 149 app : optional, ignored
150 150 This parameter is allowed only so that clear_inputhook() can be
151 151 called with a similar interface as all the ``enable_*`` methods. But
152 152 the actual value of the parameter is ignored. This uniform interface
153 153 makes it easier to have user-level entry points in the main IPython
154 154 app like :meth:`enable_gui`."""
155 155 pyos_inputhook_ptr = self.get_pyos_inputhook()
156 156 original = self.get_pyos_inputhook_as_func()
157 157 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
158 158 allow_CTRL_C()
159 159 self._reset()
160 160 return original
161 161
162 162 def clear_app_refs(self, gui=None):
163 163 """Clear IPython's internal reference to an application instance.
164 164
165 165 Whenever we create an app for a user on qt4 or wx, we hold a
166 166 reference to the app. This is needed because in some cases bad things
167 167 can happen if a user doesn't hold a reference themselves. This
168 168 method is provided to clear the references we are holding.
169 169
170 170 Parameters
171 171 ----------
172 172 gui : None or str
173 173 If None, clear all app references. If ('wx', 'qt4') clear
174 174 the app for that toolkit. References are not held for gtk or tk
175 175 as those toolkits don't have the notion of an app.
176 176 """
177 177 if gui is None:
178 178 self._apps = {}
179 179 elif gui in self._apps:
180 180 del self._apps[gui]
181 181
182 182 def enable_wx(self, app=None):
183 183 """Enable event loop integration with wxPython.
184 184
185 185 Parameters
186 186 ----------
187 187 app : WX Application, optional.
188 188 Running application to use. If not given, we probe WX for an
189 189 existing application object, and create a new one if none is found.
190 190
191 191 Notes
192 192 -----
193 193 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
194 194 the wxPython to integrate with terminal based applications like
195 195 IPython.
196 196
197 197 If ``app`` is not given we probe for an existing one, and return it if
198 198 found. If no existing app is found, we create an :class:`wx.App` as
199 199 follows::
200 200
201 201 import wx
202 202 app = wx.App(redirect=False, clearSigInt=False)
203 203 """
204 204 import wx
205 205
206 206 wx_version = V(wx.__version__).version
207 207
208 208 if wx_version < [2, 8]:
209 209 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
210 210
211 211 from IPython.lib.inputhookwx import inputhook_wx
212 212 self.set_inputhook(inputhook_wx)
213 213 self._current_gui = GUI_WX
214 214 import wx
215 215 if app is None:
216 216 app = wx.GetApp()
217 217 if app is None:
218 218 app = wx.App(redirect=False, clearSigInt=False)
219 219 app._in_event_loop = True
220 220 self._apps[GUI_WX] = app
221 221 return app
222 222
223 223 def disable_wx(self):
224 224 """Disable event loop integration with wxPython.
225 225
226 226 This merely sets PyOS_InputHook to NULL.
227 227 """
228 228 if GUI_WX in self._apps:
229 229 self._apps[GUI_WX]._in_event_loop = False
230 230 self.clear_inputhook()
231 231
232 232 def enable_qt4(self, app=None):
233 233 """Enable event loop integration with PyQt4.
234 234
235 235 Parameters
236 236 ----------
237 237 app : Qt Application, optional.
238 238 Running application to use. If not given, we probe Qt for an
239 239 existing application object, and create a new one if none is found.
240 240
241 241 Notes
242 242 -----
243 243 This methods sets the PyOS_InputHook for PyQt4, which allows
244 244 the PyQt4 to integrate with terminal based applications like
245 245 IPython.
246 246
247 247 If ``app`` is not given we probe for an existing one, and return it if
248 248 found. If no existing app is found, we create an :class:`QApplication`
249 249 as follows::
250 250
251 251 from PyQt4 import QtCore
252 252 app = QtGui.QApplication(sys.argv)
253 253 """
254 254 from IPython.lib.inputhookqt4 import create_inputhook_qt4
255 255 app, inputhook_qt4 = create_inputhook_qt4(self, app)
256 256 self.set_inputhook(inputhook_qt4)
257 257
258 258 self._current_gui = GUI_QT4
259 259 app._in_event_loop = True
260 260 self._apps[GUI_QT4] = app
261 261 return app
262 262
263 263 def disable_qt4(self):
264 264 """Disable event loop integration with PyQt4.
265 265
266 266 This merely sets PyOS_InputHook to NULL.
267 267 """
268 268 if GUI_QT4 in self._apps:
269 269 self._apps[GUI_QT4]._in_event_loop = False
270 270 self.clear_inputhook()
271 271
272 272 def enable_gtk(self, app=None):
273 273 """Enable event loop integration with PyGTK.
274 274
275 275 Parameters
276 276 ----------
277 277 app : ignored
278 278 Ignored, it's only a placeholder to keep the call signature of all
279 279 gui activation methods consistent, which simplifies the logic of
280 280 supporting magics.
281 281
282 282 Notes
283 283 -----
284 284 This methods sets the PyOS_InputHook for PyGTK, which allows
285 285 the PyGTK to integrate with terminal based applications like
286 286 IPython.
287 287 """
288 288 import gtk
289 289 try:
290 290 gtk.set_interactive(True)
291 291 self._current_gui = GUI_GTK
292 292 except AttributeError:
293 293 # For older versions of gtk, use our own ctypes version
294 294 from IPython.lib.inputhookgtk import inputhook_gtk
295 295 self.set_inputhook(inputhook_gtk)
296 296 self._current_gui = GUI_GTK
297 297
298 298 def disable_gtk(self):
299 299 """Disable event loop integration with PyGTK.
300 300
301 301 This merely sets PyOS_InputHook to NULL.
302 302 """
303 303 self.clear_inputhook()
304 304
305 305 def enable_tk(self, app=None):
306 306 """Enable event loop integration with Tk.
307 307
308 308 Parameters
309 309 ----------
310 310 app : toplevel :class:`Tkinter.Tk` widget, optional.
311 311 Running toplevel widget to use. If not given, we probe Tk for an
312 312 existing one, and create a new one if none is found.
313 313
314 314 Notes
315 315 -----
316 316 If you have already created a :class:`Tkinter.Tk` object, the only
317 317 thing done by this method is to register with the
318 318 :class:`InputHookManager`, since creating that object automatically
319 319 sets ``PyOS_InputHook``.
320 320 """
321 321 self._current_gui = GUI_TK
322 322 if app is None:
323 import tkinter
324 app = tkinter.Tk()
323 try:
324 from tkinter import Tk # Py 3
325 except ImportError:
326 from Tkinter import Tk # Py 2
327 app = Tk()
325 328 app.withdraw()
326 329 self._apps[GUI_TK] = app
327 330 return app
328 331
329 332 def disable_tk(self):
330 333 """Disable event loop integration with Tkinter.
331 334
332 335 This merely sets PyOS_InputHook to NULL.
333 336 """
334 337 self.clear_inputhook()
335 338
336 339
337 340 def enable_glut(self, app=None):
338 341 """ Enable event loop integration with GLUT.
339 342
340 343 Parameters
341 344 ----------
342 345
343 346 app : ignored
344 347 Ignored, it's only a placeholder to keep the call signature of all
345 348 gui activation methods consistent, which simplifies the logic of
346 349 supporting magics.
347 350
348 351 Notes
349 352 -----
350 353
351 354 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
352 355 integrate with terminal based applications like IPython. Due to GLUT
353 356 limitations, it is currently not possible to start the event loop
354 357 without first creating a window. You should thus not create another
355 358 window but use instead the created one. See 'gui-glut.py' in the
356 359 docs/examples/lib directory.
357 360
358 361 The default screen mode is set to:
359 362 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
360 363 """
361 364
362 365 import OpenGL.GLUT as glut
363 366 from IPython.lib.inputhookglut import glut_display_mode, \
364 367 glut_close, glut_display, \
365 368 glut_idle, inputhook_glut
366 369
367 370 if GUI_GLUT not in self._apps:
368 371 glut.glutInit( sys.argv )
369 372 glut.glutInitDisplayMode( glut_display_mode )
370 373 # This is specific to freeglut
371 374 if bool(glut.glutSetOption):
372 375 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
373 376 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
374 377 glut.glutCreateWindow( sys.argv[0] )
375 378 glut.glutReshapeWindow( 1, 1 )
376 379 glut.glutHideWindow( )
377 380 glut.glutWMCloseFunc( glut_close )
378 381 glut.glutDisplayFunc( glut_display )
379 382 glut.glutIdleFunc( glut_idle )
380 383 else:
381 384 glut.glutWMCloseFunc( glut_close )
382 385 glut.glutDisplayFunc( glut_display )
383 386 glut.glutIdleFunc( glut_idle)
384 387 self.set_inputhook( inputhook_glut )
385 388 self._current_gui = GUI_GLUT
386 389 self._apps[GUI_GLUT] = True
387 390
388 391
389 392 def disable_glut(self):
390 393 """Disable event loop integration with glut.
391 394
392 395 This sets PyOS_InputHook to NULL and set the display function to a
393 396 dummy one and set the timer to a dummy timer that will be triggered
394 397 very far in the future.
395 398 """
396 399 import OpenGL.GLUT as glut
397 400 from glut_support import glutMainLoopEvent
398 401
399 402 glut.glutHideWindow() # This is an event to be processed below
400 403 glutMainLoopEvent()
401 404 self.clear_inputhook()
402 405
403 406 def enable_pyglet(self, app=None):
404 407 """Enable event loop integration with pyglet.
405 408
406 409 Parameters
407 410 ----------
408 411 app : ignored
409 412 Ignored, it's only a placeholder to keep the call signature of all
410 413 gui activation methods consistent, which simplifies the logic of
411 414 supporting magics.
412 415
413 416 Notes
414 417 -----
415 418 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
416 419 pyglet to integrate with terminal based applications like
417 420 IPython.
418 421
419 422 """
420 423 from IPython.lib.inputhookpyglet import inputhook_pyglet
421 424 self.set_inputhook(inputhook_pyglet)
422 425 self._current_gui = GUI_PYGLET
423 426 return app
424 427
425 428 def disable_pyglet(self):
426 429 """Disable event loop integration with pyglet.
427 430
428 431 This merely sets PyOS_InputHook to NULL.
429 432 """
430 433 self.clear_inputhook()
431 434
432 435 def enable_gtk3(self, app=None):
433 436 """Enable event loop integration with Gtk3 (gir bindings).
434 437
435 438 Parameters
436 439 ----------
437 440 app : ignored
438 441 Ignored, it's only a placeholder to keep the call signature of all
439 442 gui activation methods consistent, which simplifies the logic of
440 443 supporting magics.
441 444
442 445 Notes
443 446 -----
444 447 This methods sets the PyOS_InputHook for Gtk3, which allows
445 448 the Gtk3 to integrate with terminal based applications like
446 449 IPython.
447 450 """
448 451 from IPython.lib.inputhookgtk3 import inputhook_gtk3
449 452 self.set_inputhook(inputhook_gtk3)
450 453 self._current_gui = GUI_GTK
451 454
452 455 def disable_gtk3(self):
453 456 """Disable event loop integration with PyGTK.
454 457
455 458 This merely sets PyOS_InputHook to NULL.
456 459 """
457 460 self.clear_inputhook()
458 461
459 462 def current_gui(self):
460 463 """Return a string indicating the currently active GUI or None."""
461 464 return self._current_gui
462 465
463 466 inputhook_manager = InputHookManager()
464 467
465 468 enable_wx = inputhook_manager.enable_wx
466 469 disable_wx = inputhook_manager.disable_wx
467 470 enable_qt4 = inputhook_manager.enable_qt4
468 471 disable_qt4 = inputhook_manager.disable_qt4
469 472 enable_gtk = inputhook_manager.enable_gtk
470 473 disable_gtk = inputhook_manager.disable_gtk
471 474 enable_tk = inputhook_manager.enable_tk
472 475 disable_tk = inputhook_manager.disable_tk
473 476 enable_glut = inputhook_manager.enable_glut
474 477 disable_glut = inputhook_manager.disable_glut
475 478 enable_pyglet = inputhook_manager.enable_pyglet
476 479 disable_pyglet = inputhook_manager.disable_pyglet
477 480 enable_gtk3 = inputhook_manager.enable_gtk3
478 481 disable_gtk3 = inputhook_manager.disable_gtk3
479 482 clear_inputhook = inputhook_manager.clear_inputhook
480 483 set_inputhook = inputhook_manager.set_inputhook
481 484 current_gui = inputhook_manager.current_gui
482 485 clear_app_refs = inputhook_manager.clear_app_refs
483 486
484 487 guis = {None: clear_inputhook,
485 488 GUI_NONE: clear_inputhook,
486 489 GUI_OSX: lambda app=False: None,
487 490 GUI_TK: enable_tk,
488 491 GUI_GTK: enable_gtk,
489 492 GUI_WX: enable_wx,
490 493 GUI_QT: enable_qt4, # qt3 not supported
491 494 GUI_QT4: enable_qt4,
492 495 GUI_GLUT: enable_glut,
493 496 GUI_PYGLET: enable_pyglet,
494 497 GUI_GTK3: enable_gtk3,
495 498 }
496 499
497 500
498 501 # Convenience function to switch amongst them
499 502 def enable_gui(gui=None, app=None):
500 503 """Switch amongst GUI input hooks by name.
501 504
502 505 This is just a utility wrapper around the methods of the InputHookManager
503 506 object.
504 507
505 508 Parameters
506 509 ----------
507 510 gui : optional, string or None
508 511 If None (or 'none'), clears input hook, otherwise it must be one
509 512 of the recognized GUI names (see ``GUI_*`` constants in module).
510 513
511 514 app : optional, existing application object.
512 515 For toolkits that have the concept of a global app, you can supply an
513 516 existing one. If not given, the toolkit will be probed for one, and if
514 517 none is found, a new one will be created. Note that GTK does not have
515 518 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
516 519
517 520 Returns
518 521 -------
519 522 The output of the underlying gui switch routine, typically the actual
520 523 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
521 524 one.
522 525 """
523 526 try:
524 527 gui_hook = guis[gui]
525 528 except KeyError:
526 529 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
527 530 raise ValueError(e)
528 531 return gui_hook(app)
529 532
General Comments 0
You need to be logged in to leave comments. Login now