##// END OF EJS Templates
Added an improved mainloop manager....
Fernando Perez -
Show More
@@ -1,317 +1,333 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 Inputhook management for GUI event loop integration.
4 Inputhook management for GUI event loop integration.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 import ctypes
18 import ctypes
19 import sys
19 import sys
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Code
22 # Code
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 def _dummy_mainloop(*args, **kw):
25 class _DummyMainloop(object):
26 pass
26 """A special manager to hijack GUI mainloops that is mostly a no-op.
27
28 This does have, however, special logic.
29 """
30 def __init__(self, ml, ihm, gui_type):
31 self.ml = ml
32 self.ihm = ihm
33 self.gui_type = gui_type
34
35
36 def __call__(self, *args, **kw):
37 if self.ihm.current_gui() == self.gui_type:
38 pass
39 else:
40 self.ml(*args, **kw)
27
41
28
42
29 def spin_qt4():
43 def spin_qt4():
30 from PyQt4 import QtCore, QtGui
44 from PyQt4 import QtCore, QtGui
31
45
32 app = QtCore.QCoreApplication.instance()
46 app = QtCore.QCoreApplication.instance()
33 if app is not None and app.thread == QtCore.QThread.currentThread():
47 if (app is not None) and (app.thread() == QtCore.QThread.currentThread()):
34 timer = QtCore.QTimer()
48 timer = QtCore.QTimer()
35 QtCore.QObject.connect(timer,
49 QtCore.QObject.connect(timer,
36 QtCore.SIGNAL('timeout()'),
50 QtCore.SIGNAL('timeout()'),
51 app,
37 QtCore.SLOT('quit()'))
52 QtCore.SLOT('quit()'))
38 self.timer.start(100)
53 timer.start(100)
39 QtCore.QCoreApplication.exec_()
54 QtCore.QCoreApplication.exec_()
40 timer.stop()
55 timer.stop()
41
56
42
57
43 def spin_wx():
58 def spin_wx():
44 app = wx.GetApp()
59 app = wx.GetApp()
45 if app is not None and wx.Thread_IsMain():
60 if app is not None and wx.Thread_IsMain():
46 evtloop = wx.EventLoop()
61 evtloop = wx.EventLoop()
47 ea = wx.EventLoopActivator(evtloop)
62 ea = wx.EventLoopActivator(evtloop)
48 while evtloop.Pending():
63 while evtloop.Pending():
49 evtloop.Dispatch()
64 evtloop.Dispatch()
50 app.ProcessIdle()
65 app.ProcessIdle()
51 del ea
66 del ea
52
67
53
68
54 class InputHookManager(object):
69 class InputHookManager(object):
55 """Manage PyOS_InputHook for different GUI toolkits.
70 """Manage PyOS_InputHook for different GUI toolkits.
56
71
57 This class installs various hooks under ``PyOSInputHook`` to handle
72 This class installs various hooks under ``PyOSInputHook`` to handle
58 GUI event loop integration.
73 GUI event loop integration.
59 """
74 """
60
75
61 def __init__(self):
76 def __init__(self):
62 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
77 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
63 self._apps = {}
78 self._apps = {}
64 self._reset()
79 self._reset()
65
80
66 def _reset(self):
81 def _reset(self):
67 self._callback_pyfunctype = None
82 self._callback_pyfunctype = None
68 self._callback = None
83 self._callback = None
69 self._installed = False
84 self._installed = False
70 self._current_gui = None
85 self._current_gui = None
71
86
72 def _hijack_wx(self):
87 def _hijack_wx(self):
73 """Hijack the wx mainloop so a user calling it won't cause badness."""
88 """Hijack the wx mainloop so a user calling it won't cause badness."""
74 import wx
89 import wx
75 if hasattr(wx, '_core_'): core = getattr(wx, '_core_')
90 if hasattr(wx, '_core_'): core = getattr(wx, '_core_')
76 elif hasattr(wx, '_core'): core = getattr(wx, '_core')
91 elif hasattr(wx, '_core'): core = getattr(wx, '_core')
77 else: raise AttributeError('Could not find wx core module')
92 else: raise AttributeError('Could not find wx core module')
78 orig_mainloop = core.PyApp_MainLoop
93 orig_mainloop = core.PyApp_MainLoop
79 core.PyApp_MainLoop = _dummy_mainloop
94 core.PyApp_MainLoop = _DummyMainloop
80 return orig_mainloop
95 return orig_mainloop
81
96
82 def _hijack_qt4(self):
97 def _hijack_qt4(self):
83 """Hijack the qt4 mainloop so a user calling it won't cause badness."""
98 """Hijack the qt4 mainloop so a user calling it won't cause badness."""
84 from PyQt4 import QtGui, QtCore
99 from PyQt4 import QtGui, QtCore
85 orig_mainloop = QtGui.qApp.exec_
100 orig_mainloop = QtGui.qApp.exec_
86 QtGui.qApp.exec_ = _dummy_mainloop
101 dumb_ml = _DummyMainloop(orig_mainloop, self, 'qt4')
87 QtGui.QApplication.exec_ = _dummy_mainloop
102 QtGui.qApp.exec_ = dumb_ml
88 QtCore.QCoreApplication.exec_ = _dummy_mainloop
103 QtGui.QApplication.exec_ = dumb_ml
104 QtCore.QCoreApplication.exec_ = dumb_ml
89 return orig_mainloop
105 return orig_mainloop
90
106
91 def _hijack_gtk(self):
107 def _hijack_gtk(self):
92 """Hijack the gtk mainloop so a user calling it won't cause badness."""
108 """Hijack the gtk mainloop so a user calling it won't cause badness."""
93 import gtk
109 import gtk
94 orig_mainloop = gtk.main
110 orig_mainloop = gtk.main
95 gtk.mainloop = _dummy_mainloop
111 gtk.mainloop = _DummyMainloop
96 gtk.main = _dummy_mainloop
112 gtk.main = _DummyMainloop
97 return orig_mainloop
113 return orig_mainloop
98
114
99 def _hijack_tk(self):
115 def _hijack_tk(self):
100 """Hijack the tk mainloop so a user calling it won't cause badness."""
116 """Hijack the tk mainloop so a user calling it won't cause badness."""
101 import Tkinter
117 import Tkinter
102 Tkinter.Misc.mainloop = _dummy_mainloop
118 Tkinter.Misc.mainloop = _DummyMainloop
103 Tkinter.mainloop = _dummy_mainloop
119 Tkinter.mainloop = _DummyMainloop
104
120
105 def get_pyos_inputhook(self):
121 def get_pyos_inputhook(self):
106 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
122 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
107 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
123 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
108
124
109 def get_pyos_inputhook_as_func(self):
125 def get_pyos_inputhook_as_func(self):
110 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
126 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
111 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
127 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
112
128
113 def set_inputhook(self, callback):
129 def set_inputhook(self, callback):
114 """Set PyOS_InputHook to callback and return the previous one."""
130 """Set PyOS_InputHook to callback and return the previous one."""
115 self._callback = callback
131 self._callback = callback
116 self._callback_pyfunctype = self.PYFUNC(callback)
132 self._callback_pyfunctype = self.PYFUNC(callback)
117 pyos_inputhook_ptr = self.get_pyos_inputhook()
133 pyos_inputhook_ptr = self.get_pyos_inputhook()
118 original = self.get_pyos_inputhook_as_func()
134 original = self.get_pyos_inputhook_as_func()
119 pyos_inputhook_ptr.value = \
135 pyos_inputhook_ptr.value = \
120 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
136 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
121 self._installed = True
137 self._installed = True
122 return original
138 return original
123
139
124 def clear_inputhook(self):
140 def clear_inputhook(self):
125 """Set PyOS_InputHook to NULL and return the previous one."""
141 """Set PyOS_InputHook to NULL and return the previous one."""
126 pyos_inputhook_ptr = self.get_pyos_inputhook()
142 pyos_inputhook_ptr = self.get_pyos_inputhook()
127 original = self.get_pyos_inputhook_as_func()
143 original = self.get_pyos_inputhook_as_func()
128 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
144 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
129 self._reset()
145 self._reset()
130 return original
146 return original
131
147
132 def clear_app_refs(self, gui=None):
148 def clear_app_refs(self, gui=None):
133 """Clear IPython's internal reference to an application instance.
149 """Clear IPython's internal reference to an application instance.
134
150
135 Whenever we create an app for a user on qt4 or wx, we hold a
151 Whenever we create an app for a user on qt4 or wx, we hold a
136 reference to the app. This is needed because in some cases bad things
152 reference to the app. This is needed because in some cases bad things
137 can happen if a user doesn't hold a reference themselves. This
153 can happen if a user doesn't hold a reference themselves. This
138 method is provided to clear the references we are holding.
154 method is provided to clear the references we are holding.
139
155
140 Parameters
156 Parameters
141 ----------
157 ----------
142 gui : None or str
158 gui : None or str
143 If None, clear all app references. If ('wx', 'qt4') clear
159 If None, clear all app references. If ('wx', 'qt4') clear
144 the app for that toolkit. References are not held for gtk or tk
160 the app for that toolkit. References are not held for gtk or tk
145 as those toolkits don't have the notion of an app.
161 as those toolkits don't have the notion of an app.
146 """
162 """
147 if gui is None:
163 if gui is None:
148 self._apps = {}
164 self._apps = {}
149 elif self._apps.has_key(gui):
165 elif self._apps.has_key(gui):
150 del self._apps[gui]
166 del self._apps[gui]
151
167
152 def enable_wx(self, app=False):
168 def enable_wx(self, app=False):
153 """Enable event loop integration with wxPython.
169 """Enable event loop integration with wxPython.
154
170
155 Parameters
171 Parameters
156 ----------
172 ----------
157 app : bool
173 app : bool
158 Create a running application object or not.
174 Create a running application object or not.
159
175
160 Notes
176 Notes
161 -----
177 -----
162 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
178 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
163 the wxPython to integrate with terminal based applications like
179 the wxPython to integrate with terminal based applications like
164 IPython.
180 IPython.
165
181
166 If ``app`` is True, we create an :class:`wx.App` as follows::
182 If ``app`` is True, we create an :class:`wx.App` as follows::
167
183
168 import wx
184 import wx
169 app = wx.App(redirect=False, clearSigInt=False)
185 app = wx.App(redirect=False, clearSigInt=False)
170
186
171 Both options this constructor are important for things to work
187 Both options this constructor are important for things to work
172 properly in an interactive context.
188 properly in an interactive context.
173
189
174 But, we first check to see if an application has already been
190 But, we first check to see if an application has already been
175 created. If so, we simply return that instance.
191 created. If so, we simply return that instance.
176 """
192 """
177 from IPython.lib.inputhookwx import inputhook_wx
193 from IPython.lib.inputhookwx import inputhook_wx
178 self.set_inputhook(inputhook_wx)
194 self.set_inputhook(inputhook_wx)
179 self._current_gui = 'wx'
195 self._current_gui = 'wx'
180 self._hijack_wx()
196 self._hijack_wx()
181 if app:
197 if app:
182 import wx
198 import wx
183 app = wx.GetApp()
199 app = wx.GetApp()
184 if app is None:
200 if app is None:
185 app = wx.App(redirect=False, clearSigInt=False)
201 app = wx.App(redirect=False, clearSigInt=False)
186 self._apps['wx'] = app
202 self._apps['wx'] = app
187 return app
203 return app
188
204
189 def disable_wx(self):
205 def disable_wx(self):
190 """Disable event loop integration with wxPython.
206 """Disable event loop integration with wxPython.
191
207
192 This merely sets PyOS_InputHook to NULL.
208 This merely sets PyOS_InputHook to NULL.
193 """
209 """
194 self.clear_inputhook()
210 self.clear_inputhook()
195
211
196 def enable_qt4(self, app=False):
212 def enable_qt4(self, app=False):
197 """Enable event loop integration with PyQt4.
213 """Enable event loop integration with PyQt4.
198
214
199 Parameters
215 Parameters
200 ----------
216 ----------
201 app : bool
217 app : bool
202 Create a running application object or not.
218 Create a running application object or not.
203
219
204 Notes
220 Notes
205 -----
221 -----
206 This methods sets the PyOS_InputHook for PyQt4, which allows
222 This methods sets the PyOS_InputHook for PyQt4, which allows
207 the PyQt4 to integrate with terminal based applications like
223 the PyQt4 to integrate with terminal based applications like
208 IPython.
224 IPython.
209
225
210 If ``app`` is True, we create an :class:`QApplication` as follows::
226 If ``app`` is True, we create an :class:`QApplication` as follows::
211
227
212 from PyQt4 import QtCore
228 from PyQt4 import QtCore
213 app = QtGui.QApplication(sys.argv)
229 app = QtGui.QApplication(sys.argv)
214
230
215 But, we first check to see if an application has already been
231 But, we first check to see if an application has already been
216 created. If so, we simply return that instance.
232 created. If so, we simply return that instance.
217 """
233 """
218 from PyQt4 import QtCore
234 from PyQt4 import QtCore
219 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
235 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
220 # was set when QtCore was imported, but if it ever got removed,
236 # was set when QtCore was imported, but if it ever got removed,
221 # you couldn't reset it. For earlier versions we can
237 # you couldn't reset it. For earlier versions we can
222 # probably implement a ctypes version.
238 # probably implement a ctypes version.
223 try:
239 try:
224 QtCore.pyqtRestoreInputHook()
240 QtCore.pyqtRestoreInputHook()
225 except AttributeError:
241 except AttributeError:
226 pass
242 pass
227 self._current_gui = 'qt4'
243 self._current_gui = 'qt4'
228 self._hijack_qt4()
244 self._hijack_qt4()
229 if app:
245 if app:
230 from PyQt4 import QtGui
246 from PyQt4 import QtGui
231 app = QtCore.QCoreApplication.instance()
247 app = QtCore.QCoreApplication.instance()
232 if app is None:
248 if app is None:
233 app = QtGui.QApplication(sys.argv)
249 app = QtGui.QApplication(sys.argv)
234 self._apps['qt4'] = app
250 self._apps['qt4'] = app
235 return app
251 return app
236
252
237 def disable_qt4(self):
253 def disable_qt4(self):
238 """Disable event loop integration with PyQt4.
254 """Disable event loop integration with PyQt4.
239
255
240 This merely sets PyOS_InputHook to NULL.
256 This merely sets PyOS_InputHook to NULL.
241 """
257 """
242 self.clear_inputhook()
258 self.clear_inputhook()
243
259
244 def enable_gtk(self, app=False):
260 def enable_gtk(self, app=False):
245 """Enable event loop integration with PyGTK.
261 """Enable event loop integration with PyGTK.
246
262
247 Parameters
263 Parameters
248 ----------
264 ----------
249 app : bool
265 app : bool
250 Create a running application object or not. Because gtk does't
266 Create a running application object or not. Because gtk does't
251 have an app class, this does nothing.
267 have an app class, this does nothing.
252
268
253 Notes
269 Notes
254 -----
270 -----
255 This methods sets the PyOS_InputHook for PyGTK, which allows
271 This methods sets the PyOS_InputHook for PyGTK, which allows
256 the PyGTK to integrate with terminal based applications like
272 the PyGTK to integrate with terminal based applications like
257 IPython.
273 IPython.
258 """
274 """
259 import gtk
275 import gtk
260 try:
276 try:
261 gtk.set_interactive(True)
277 gtk.set_interactive(True)
262 self._current_gui = 'gtk'
278 self._current_gui = 'gtk'
263 except AttributeError:
279 except AttributeError:
264 # For older versions of gtk, use our own ctypes version
280 # For older versions of gtk, use our own ctypes version
265 from IPython.lib.inputhookgtk import inputhook_gtk
281 from IPython.lib.inputhookgtk import inputhook_gtk
266 self.set_inputhook(inputhook_gtk)
282 self.set_inputhook(inputhook_gtk)
267 self._current_gui = 'gtk'
283 self._current_gui = 'gtk'
268 self._hijack_gtk()
284 self._hijack_gtk()
269
285
270 def disable_gtk(self):
286 def disable_gtk(self):
271 """Disable event loop integration with PyGTK.
287 """Disable event loop integration with PyGTK.
272
288
273 This merely sets PyOS_InputHook to NULL.
289 This merely sets PyOS_InputHook to NULL.
274 """
290 """
275 self.clear_inputhook()
291 self.clear_inputhook()
276
292
277 def enable_tk(self, app=False):
293 def enable_tk(self, app=False):
278 """Enable event loop integration with Tk.
294 """Enable event loop integration with Tk.
279
295
280 Parameters
296 Parameters
281 ----------
297 ----------
282 app : bool
298 app : bool
283 Create a running application object or not.
299 Create a running application object or not.
284
300
285 Notes
301 Notes
286 -----
302 -----
287 Currently this is a no-op as creating a :class:`Tkinter.Tk` object
303 Currently this is a no-op as creating a :class:`Tkinter.Tk` object
288 sets ``PyOS_InputHook``.
304 sets ``PyOS_InputHook``.
289 """
305 """
290 self._current_gui = 'tk'
306 self._current_gui = 'tk'
291 self._hijack_tk()
307 self._hijack_tk()
292
308
293 def disable_tk(self):
309 def disable_tk(self):
294 """Disable event loop integration with Tkinter.
310 """Disable event loop integration with Tkinter.
295
311
296 This merely sets PyOS_InputHook to NULL.
312 This merely sets PyOS_InputHook to NULL.
297 """
313 """
298 self.clear_inputhook()
314 self.clear_inputhook()
299
315
300 def current_gui(self):
316 def current_gui(self):
301 """Return a string indicating the currently active GUI or None."""
317 """Return a string indicating the currently active GUI or None."""
302 return self._current_gui
318 return self._current_gui
303
319
304 inputhook_manager = InputHookManager()
320 inputhook_manager = InputHookManager()
305
321
306 enable_wx = inputhook_manager.enable_wx
322 enable_wx = inputhook_manager.enable_wx
307 disable_wx = inputhook_manager.disable_wx
323 disable_wx = inputhook_manager.disable_wx
308 enable_qt4 = inputhook_manager.enable_qt4
324 enable_qt4 = inputhook_manager.enable_qt4
309 disable_qt4 = inputhook_manager.disable_qt4
325 disable_qt4 = inputhook_manager.disable_qt4
310 enable_gtk = inputhook_manager.enable_gtk
326 enable_gtk = inputhook_manager.enable_gtk
311 disable_gtk = inputhook_manager.disable_gtk
327 disable_gtk = inputhook_manager.disable_gtk
312 enable_tk = inputhook_manager.enable_tk
328 enable_tk = inputhook_manager.enable_tk
313 disable_tk = inputhook_manager.disable_tk
329 disable_tk = inputhook_manager.disable_tk
314 clear_inputhook = inputhook_manager.clear_inputhook
330 clear_inputhook = inputhook_manager.clear_inputhook
315 set_inputhook = inputhook_manager.set_inputhook
331 set_inputhook = inputhook_manager.set_inputhook
316 current_gui = inputhook_manager.current_gui
332 current_gui = inputhook_manager.current_gui
317 clear_app_refs = inputhook_manager.clear_app_refs
333 clear_app_refs = inputhook_manager.clear_app_refs
General Comments 0
You need to be logged in to leave comments. Login now