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