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