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