##// END OF EJS Templates
Missing files added
Nicolas Rougier -
Show More
@@ -1,344 +1,382 b''
1 # coding: utf-8
1 # coding: utf-8
2 """
2 """
3 Inputhook management for GUI event loop integration.
3 Inputhook management for GUI event loop integration.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import ctypes
17 import ctypes
18 import sys
18 import sys
19 import warnings
19 import warnings
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Constants
22 # Constants
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 # Constants for identifying the GUI toolkits.
25 # Constants for identifying the GUI toolkits.
26 GUI_WX = 'wx'
26 GUI_WX = 'wx'
27 GUI_QT = 'qt'
27 GUI_QT = 'qt'
28 GUI_QT4 = 'qt4'
28 GUI_QT4 = 'qt4'
29 GUI_GTK = 'gtk'
29 GUI_GTK = 'gtk'
30 GUI_TK = 'tk'
30 GUI_TK = 'tk'
31 GUI_OSX = 'osx'
31 GUI_OSX = 'osx'
32 GUI_PYGLET = 'pyglet'
32
33
33 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
34 # Utility classes
35 # Utility classes
35 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
36
37
37
38
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39 # Main InputHookManager class
40 # Main InputHookManager class
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41
42
42
43
43 class InputHookManager(object):
44 class InputHookManager(object):
44 """Manage PyOS_InputHook for different GUI toolkits.
45 """Manage PyOS_InputHook for different GUI toolkits.
45
46
46 This class installs various hooks under ``PyOSInputHook`` to handle
47 This class installs various hooks under ``PyOSInputHook`` to handle
47 GUI event loop integration.
48 GUI event loop integration.
48 """
49 """
49
50
50 def __init__(self):
51 def __init__(self):
51 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
52 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
52 self._apps = {}
53 self._apps = {}
53 self._reset()
54 self._reset()
54
55
55 def _reset(self):
56 def _reset(self):
56 self._callback_pyfunctype = None
57 self._callback_pyfunctype = None
57 self._callback = None
58 self._callback = None
58 self._installed = False
59 self._installed = False
59 self._current_gui = None
60 self._current_gui = None
60
61
61 def get_pyos_inputhook(self):
62 def get_pyos_inputhook(self):
62 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
63 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
63 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
64 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
64
65
65 def get_pyos_inputhook_as_func(self):
66 def get_pyos_inputhook_as_func(self):
66 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
67 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
67 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
68 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
68
69
69 def set_inputhook(self, callback):
70 def set_inputhook(self, callback):
70 """Set PyOS_InputHook to callback and return the previous one."""
71 """Set PyOS_InputHook to callback and return the previous one."""
71 self._callback = callback
72 self._callback = callback
72 self._callback_pyfunctype = self.PYFUNC(callback)
73 self._callback_pyfunctype = self.PYFUNC(callback)
73 pyos_inputhook_ptr = self.get_pyos_inputhook()
74 pyos_inputhook_ptr = self.get_pyos_inputhook()
74 original = self.get_pyos_inputhook_as_func()
75 original = self.get_pyos_inputhook_as_func()
75 pyos_inputhook_ptr.value = \
76 pyos_inputhook_ptr.value = \
76 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
77 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
77 self._installed = True
78 self._installed = True
78 return original
79 return original
79
80
80 def clear_inputhook(self, app=None):
81 def clear_inputhook(self, app=None):
81 """Set PyOS_InputHook to NULL and return the previous one.
82 """Set PyOS_InputHook to NULL and return the previous one.
82
83
83 Parameters
84 Parameters
84 ----------
85 ----------
85 app : optional, ignored
86 app : optional, ignored
86 This parameter is allowed only so that clear_inputhook() can be
87 This parameter is allowed only so that clear_inputhook() can be
87 called with a similar interface as all the ``enable_*`` methods. But
88 called with a similar interface as all the ``enable_*`` methods. But
88 the actual value of the parameter is ignored. This uniform interface
89 the actual value of the parameter is ignored. This uniform interface
89 makes it easier to have user-level entry points in the main IPython
90 makes it easier to have user-level entry points in the main IPython
90 app like :meth:`enable_gui`."""
91 app like :meth:`enable_gui`."""
91 pyos_inputhook_ptr = self.get_pyos_inputhook()
92 pyos_inputhook_ptr = self.get_pyos_inputhook()
92 original = self.get_pyos_inputhook_as_func()
93 original = self.get_pyos_inputhook_as_func()
93 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
94 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
94 self._reset()
95 self._reset()
95 return original
96 return original
96
97
97 def clear_app_refs(self, gui=None):
98 def clear_app_refs(self, gui=None):
98 """Clear IPython's internal reference to an application instance.
99 """Clear IPython's internal reference to an application instance.
99
100
100 Whenever we create an app for a user on qt4 or wx, we hold a
101 Whenever we create an app for a user on qt4 or wx, we hold a
101 reference to the app. This is needed because in some cases bad things
102 reference to the app. This is needed because in some cases bad things
102 can happen if a user doesn't hold a reference themselves. This
103 can happen if a user doesn't hold a reference themselves. This
103 method is provided to clear the references we are holding.
104 method is provided to clear the references we are holding.
104
105
105 Parameters
106 Parameters
106 ----------
107 ----------
107 gui : None or str
108 gui : None or str
108 If None, clear all app references. If ('wx', 'qt4') clear
109 If None, clear all app references. If ('wx', 'qt4') clear
109 the app for that toolkit. References are not held for gtk or tk
110 the app for that toolkit. References are not held for gtk or tk
110 as those toolkits don't have the notion of an app.
111 as those toolkits don't have the notion of an app.
111 """
112 """
112 if gui is None:
113 if gui is None:
113 self._apps = {}
114 self._apps = {}
114 elif self._apps.has_key(gui):
115 elif self._apps.has_key(gui):
115 del self._apps[gui]
116 del self._apps[gui]
116
117
117 def enable_wx(self, app=None):
118 def enable_wx(self, app=None):
118 """Enable event loop integration with wxPython.
119 """Enable event loop integration with wxPython.
119
120
120 Parameters
121 Parameters
121 ----------
122 ----------
122 app : WX Application, optional.
123 app : WX Application, optional.
123 Running application to use. If not given, we probe WX for an
124 Running application to use. If not given, we probe WX for an
124 existing application object, and create a new one if none is found.
125 existing application object, and create a new one if none is found.
125
126
126 Notes
127 Notes
127 -----
128 -----
128 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
129 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
129 the wxPython to integrate with terminal based applications like
130 the wxPython to integrate with terminal based applications like
130 IPython.
131 IPython.
131
132
132 If ``app`` is not given we probe for an existing one, and return it if
133 If ``app`` is not given we probe for an existing one, and return it if
133 found. If no existing app is found, we create an :class:`wx.App` as
134 found. If no existing app is found, we create an :class:`wx.App` as
134 follows::
135 follows::
135
136
136 import wx
137 import wx
137 app = wx.App(redirect=False, clearSigInt=False)
138 app = wx.App(redirect=False, clearSigInt=False)
138 """
139 """
139 from IPython.lib.inputhookwx import inputhook_wx
140 from IPython.lib.inputhookwx import inputhook_wx
140 self.set_inputhook(inputhook_wx)
141 self.set_inputhook(inputhook_wx)
141 self._current_gui = GUI_WX
142 self._current_gui = GUI_WX
142 import wx
143 import wx
143 if app is None:
144 if app is None:
144 app = wx.GetApp()
145 app = wx.GetApp()
145 if app is None:
146 if app is None:
146 app = wx.App(redirect=False, clearSigInt=False)
147 app = wx.App(redirect=False, clearSigInt=False)
147 app._in_event_loop = True
148 app._in_event_loop = True
148 self._apps[GUI_WX] = app
149 self._apps[GUI_WX] = app
149 return app
150 return app
150
151
151 def disable_wx(self):
152 def disable_wx(self):
152 """Disable event loop integration with wxPython.
153 """Disable event loop integration with wxPython.
153
154
154 This merely sets PyOS_InputHook to NULL.
155 This merely sets PyOS_InputHook to NULL.
155 """
156 """
156 if self._apps.has_key(GUI_WX):
157 if self._apps.has_key(GUI_WX):
157 self._apps[GUI_WX]._in_event_loop = False
158 self._apps[GUI_WX]._in_event_loop = False
158 self.clear_inputhook()
159 self.clear_inputhook()
159
160
160 def enable_qt4(self, app=None):
161 def enable_qt4(self, app=None):
161 """Enable event loop integration with PyQt4.
162 """Enable event loop integration with PyQt4.
162
163
163 Parameters
164 Parameters
164 ----------
165 ----------
165 app : Qt Application, optional.
166 app : Qt Application, optional.
166 Running application to use. If not given, we probe Qt for an
167 Running application to use. If not given, we probe Qt for an
167 existing application object, and create a new one if none is found.
168 existing application object, and create a new one if none is found.
168
169
169 Notes
170 Notes
170 -----
171 -----
171 This methods sets the PyOS_InputHook for PyQt4, which allows
172 This methods sets the PyOS_InputHook for PyQt4, which allows
172 the PyQt4 to integrate with terminal based applications like
173 the PyQt4 to integrate with terminal based applications like
173 IPython.
174 IPython.
174
175
175 If ``app`` is not given we probe for an existing one, and return it if
176 If ``app`` is not given we probe for an existing one, and return it if
176 found. If no existing app is found, we create an :class:`QApplication`
177 found. If no existing app is found, we create an :class:`QApplication`
177 as follows::
178 as follows::
178
179
179 from PyQt4 import QtCore
180 from PyQt4 import QtCore
180 app = QtGui.QApplication(sys.argv)
181 app = QtGui.QApplication(sys.argv)
181 """
182 """
182 from IPython.external.qt_for_kernel import QtCore, QtGui
183 from IPython.external.qt_for_kernel import QtCore, QtGui
183
184
184 if 'pyreadline' in sys.modules:
185 if 'pyreadline' in sys.modules:
185 # see IPython GitHub Issue #281 for more info on this issue
186 # see IPython GitHub Issue #281 for more info on this issue
186 # Similar intermittent behavior has been reported on OSX,
187 # Similar intermittent behavior has been reported on OSX,
187 # but not consistently reproducible
188 # but not consistently reproducible
188 warnings.warn("""PyReadline's inputhook can conflict with Qt, causing delays
189 warnings.warn("""PyReadline's inputhook can conflict with Qt, causing delays
189 in interactive input. If you do see this issue, we recommend using another GUI
190 in interactive input. If you do see this issue, we recommend using another GUI
190 toolkit if you can, or disable readline with the configuration option
191 toolkit if you can, or disable readline with the configuration option
191 'TerminalInteractiveShell.readline_use=False', specified in a config file or
192 'TerminalInteractiveShell.readline_use=False', specified in a config file or
192 at the command-line""",
193 at the command-line""",
193 RuntimeWarning)
194 RuntimeWarning)
194
195
195 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
196 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
196 # was set when QtCore was imported, but if it ever got removed,
197 # was set when QtCore was imported, but if it ever got removed,
197 # you couldn't reset it. For earlier versions we can
198 # you couldn't reset it. For earlier versions we can
198 # probably implement a ctypes version.
199 # probably implement a ctypes version.
199 try:
200 try:
200 QtCore.pyqtRestoreInputHook()
201 QtCore.pyqtRestoreInputHook()
201 except AttributeError:
202 except AttributeError:
202 pass
203 pass
203
204
204 self._current_gui = GUI_QT4
205 self._current_gui = GUI_QT4
205 if app is None:
206 if app is None:
206 app = QtCore.QCoreApplication.instance()
207 app = QtCore.QCoreApplication.instance()
207 if app is None:
208 if app is None:
208 app = QtGui.QApplication([" "])
209 app = QtGui.QApplication([" "])
209 app._in_event_loop = True
210 app._in_event_loop = True
210 self._apps[GUI_QT4] = app
211 self._apps[GUI_QT4] = app
211 return app
212 return app
212
213
213 def disable_qt4(self):
214 def disable_qt4(self):
214 """Disable event loop integration with PyQt4.
215 """Disable event loop integration with PyQt4.
215
216
216 This merely sets PyOS_InputHook to NULL.
217 This merely sets PyOS_InputHook to NULL.
217 """
218 """
218 if self._apps.has_key(GUI_QT4):
219 if self._apps.has_key(GUI_QT4):
219 self._apps[GUI_QT4]._in_event_loop = False
220 self._apps[GUI_QT4]._in_event_loop = False
220 self.clear_inputhook()
221 self.clear_inputhook()
221
222
222 def enable_gtk(self, app=None):
223 def enable_gtk(self, app=None):
223 """Enable event loop integration with PyGTK.
224 """Enable event loop integration with PyGTK.
224
225
225 Parameters
226 Parameters
226 ----------
227 ----------
227 app : ignored
228 app : ignored
228 Ignored, it's only a placeholder to keep the call signature of all
229 Ignored, it's only a placeholder to keep the call signature of all
229 gui activation methods consistent, which simplifies the logic of
230 gui activation methods consistent, which simplifies the logic of
230 supporting magics.
231 supporting magics.
231
232
232 Notes
233 Notes
233 -----
234 -----
234 This methods sets the PyOS_InputHook for PyGTK, which allows
235 This methods sets the PyOS_InputHook for PyGTK, which allows
235 the PyGTK to integrate with terminal based applications like
236 the PyGTK to integrate with terminal based applications like
236 IPython.
237 IPython.
237 """
238 """
238 import gtk
239 import gtk
239 try:
240 try:
240 gtk.set_interactive(True)
241 gtk.set_interactive(True)
241 self._current_gui = GUI_GTK
242 self._current_gui = GUI_GTK
242 except AttributeError:
243 except AttributeError:
243 # For older versions of gtk, use our own ctypes version
244 # For older versions of gtk, use our own ctypes version
244 from IPython.lib.inputhookgtk import inputhook_gtk
245 from IPython.lib.inputhookgtk import inputhook_gtk
245 self.set_inputhook(inputhook_gtk)
246 self.set_inputhook(inputhook_gtk)
246 self._current_gui = GUI_GTK
247 self._current_gui = GUI_GTK
247
248
248 def disable_gtk(self):
249 def disable_gtk(self):
249 """Disable event loop integration with PyGTK.
250 """Disable event loop integration with PyGTK.
250
251
251 This merely sets PyOS_InputHook to NULL.
252 This merely sets PyOS_InputHook to NULL.
252 """
253 """
253 self.clear_inputhook()
254 self.clear_inputhook()
254
255
255 def enable_tk(self, app=None):
256 def enable_tk(self, app=None):
256 """Enable event loop integration with Tk.
257 """Enable event loop integration with Tk.
257
258
258 Parameters
259 Parameters
259 ----------
260 ----------
260 app : toplevel :class:`Tkinter.Tk` widget, optional.
261 app : toplevel :class:`Tkinter.Tk` widget, optional.
261 Running toplevel widget to use. If not given, we probe Tk for an
262 Running toplevel widget to use. If not given, we probe Tk for an
262 existing one, and create a new one if none is found.
263 existing one, and create a new one if none is found.
263
264
264 Notes
265 Notes
265 -----
266 -----
266 If you have already created a :class:`Tkinter.Tk` object, the only
267 If you have already created a :class:`Tkinter.Tk` object, the only
267 thing done by this method is to register with the
268 thing done by this method is to register with the
268 :class:`InputHookManager`, since creating that object automatically
269 :class:`InputHookManager`, since creating that object automatically
269 sets ``PyOS_InputHook``.
270 sets ``PyOS_InputHook``.
270 """
271 """
271 self._current_gui = GUI_TK
272 self._current_gui = GUI_TK
272 if app is None:
273 if app is None:
273 import Tkinter
274 import Tkinter
274 app = Tkinter.Tk()
275 app = Tkinter.Tk()
275 app.withdraw()
276 app.withdraw()
276 self._apps[GUI_TK] = app
277 self._apps[GUI_TK] = app
277 return app
278 return app
278
279
279 def disable_tk(self):
280 def disable_tk(self):
280 """Disable event loop integration with Tkinter.
281 """Disable event loop integration with Tkinter.
281
282
282 This merely sets PyOS_InputHook to NULL.
283 This merely sets PyOS_InputHook to NULL.
283 """
284 """
284 self.clear_inputhook()
285 self.clear_inputhook()
285
286
287
288
289 def enable_pyglet(self, app=None):
290 """Enable event loop integration with pyglet.
291
292 Parameters
293 ----------
294 app : ignored
295 Ignored, it's only a placeholder to keep the call signature of all
296 gui activation methods consistent, which simplifies the logic of
297 supporting magics.
298
299 Notes
300 -----
301 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
302 pyglet to integrate with terminal based applications like
303 IPython.
304
305 """
306 import pyglet
307 from IPython.lib.inputhookpyglet import inputhook_pyglet
308 self.set_inputhook(inputhook_pyglet)
309 self._current_gui = GUI_PYGLET
310 return app
311
312 def disable_pyglet(self):
313 """Disable event loop integration with pyglet.
314
315 This merely sets PyOS_InputHook to NULL.
316 """
317 self.clear_inputhook()
318
319
286 def current_gui(self):
320 def current_gui(self):
287 """Return a string indicating the currently active GUI or None."""
321 """Return a string indicating the currently active GUI or None."""
288 return self._current_gui
322 return self._current_gui
289
323
290 inputhook_manager = InputHookManager()
324 inputhook_manager = InputHookManager()
291
325
292 enable_wx = inputhook_manager.enable_wx
326 enable_wx = inputhook_manager.enable_wx
293 disable_wx = inputhook_manager.disable_wx
327 disable_wx = inputhook_manager.disable_wx
294 enable_qt4 = inputhook_manager.enable_qt4
328 enable_qt4 = inputhook_manager.enable_qt4
295 disable_qt4 = inputhook_manager.disable_qt4
329 disable_qt4 = inputhook_manager.disable_qt4
296 enable_gtk = inputhook_manager.enable_gtk
330 enable_gtk = inputhook_manager.enable_gtk
297 disable_gtk = inputhook_manager.disable_gtk
331 disable_gtk = inputhook_manager.disable_gtk
298 enable_tk = inputhook_manager.enable_tk
332 enable_tk = inputhook_manager.enable_tk
299 disable_tk = inputhook_manager.disable_tk
333 disable_tk = inputhook_manager.disable_tk
334 enable_pyglet = inputhook_manager.enable_pyglet
335 disable_pyglet = inputhook_manager.disable_pyglet
300 clear_inputhook = inputhook_manager.clear_inputhook
336 clear_inputhook = inputhook_manager.clear_inputhook
301 set_inputhook = inputhook_manager.set_inputhook
337 set_inputhook = inputhook_manager.set_inputhook
302 current_gui = inputhook_manager.current_gui
338 current_gui = inputhook_manager.current_gui
303 clear_app_refs = inputhook_manager.clear_app_refs
339 clear_app_refs = inputhook_manager.clear_app_refs
304
340
305
341
306 # Convenience function to switch amongst them
342 # Convenience function to switch amongst them
307 def enable_gui(gui=None, app=None):
343 def enable_gui(gui=None, app=None):
308 """Switch amongst GUI input hooks by name.
344 """Switch amongst GUI input hooks by name.
309
345
310 This is just a utility wrapper around the methods of the InputHookManager
346 This is just a utility wrapper around the methods of the InputHookManager
311 object.
347 object.
312
348
313 Parameters
349 Parameters
314 ----------
350 ----------
315 gui : optional, string or None
351 gui : optional, string or None
316 If None, clears input hook, otherwise it must be one of the recognized
352 If None, clears input hook, otherwise it must be one of the recognized
317 GUI names (see ``GUI_*`` constants in module).
353 GUI names (see ``GUI_*`` constants in module).
318
354
319 app : optional, existing application object.
355 app : optional, existing application object.
320 For toolkits that have the concept of a global app, you can supply an
356 For toolkits that have the concept of a global app, you can supply an
321 existing one. If not given, the toolkit will be probed for one, and if
357 existing one. If not given, the toolkit will be probed for one, and if
322 none is found, a new one will be created. Note that GTK does not have
358 none is found, a new one will be created. Note that GTK does not have
323 this concept, and passing an app if `gui`=="GTK" will raise an error.
359 this concept, and passing an app if `gui`=="GTK" will raise an error.
324
360
325 Returns
361 Returns
326 -------
362 -------
327 The output of the underlying gui switch routine, typically the actual
363 The output of the underlying gui switch routine, typically the actual
328 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
364 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
329 one.
365 one.
330 """
366 """
331 guis = {None: clear_inputhook,
367 guis = {None: clear_inputhook,
332 GUI_OSX: lambda app=False: None,
368 GUI_OSX: lambda app=False: None,
333 GUI_TK: enable_tk,
369 GUI_TK: enable_tk,
334 GUI_GTK: enable_gtk,
370 GUI_GTK: enable_gtk,
335 GUI_WX: enable_wx,
371 GUI_WX: enable_wx,
336 GUI_QT: enable_qt4, # qt3 not supported
372 GUI_QT: enable_qt4, # qt3 not supported
337 GUI_QT4: enable_qt4 }
373 GUI_QT4: enable_qt4,
374 GUI_PYGLET: enable_pyglet,
375 }
338 try:
376 try:
339 gui_hook = guis[gui]
377 gui_hook = guis[gui]
340 except KeyError:
378 except KeyError:
341 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
379 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
342 raise ValueError(e)
380 raise ValueError(e)
343 return gui_hook(app)
381 return gui_hook(app)
344
382
General Comments 0
You need to be logged in to leave comments. Login now