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