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