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