##// END OF EJS Templates
Added code for the GLUT interactive session
Nicolas Rougier -
Show More
@@ -1,29 +1,30 b''
1 1 # encoding: utf-8
2 2 """
3 3 Extra capabilities for IPython
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 from IPython.lib.inputhook import (
18 18 enable_wx, disable_wx,
19 19 enable_gtk, disable_gtk,
20 20 enable_qt4, disable_qt4,
21 21 enable_tk, disable_tk,
22 enable_glut, disable_glut,
22 23 enable_pyglet, disable_pyglet,
23 24 set_inputhook, clear_inputhook,
24 25 current_gui
25 26 )
26 27
27 28 #-----------------------------------------------------------------------------
28 29 # Code
29 30 #-----------------------------------------------------------------------------
@@ -1,382 +1,521 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 sys
19 19 import warnings
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Constants
23 23 #-----------------------------------------------------------------------------
24 24
25 25 # Constants for identifying the GUI toolkits.
26 26 GUI_WX = 'wx'
27 27 GUI_QT = 'qt'
28 28 GUI_QT4 = 'qt4'
29 29 GUI_GTK = 'gtk'
30 30 GUI_TK = 'tk'
31 31 GUI_OSX = 'osx'
32 GUI_GLUT = 'glut'
32 33 GUI_PYGLET = 'pyglet'
33 34
34 35 #-----------------------------------------------------------------------------
35 36 # Utility classes
36 37 #-----------------------------------------------------------------------------
37 38
38 39
39 40 #-----------------------------------------------------------------------------
40 41 # Main InputHookManager class
41 42 #-----------------------------------------------------------------------------
42 43
43 44
44 45 class InputHookManager(object):
45 46 """Manage PyOS_InputHook for different GUI toolkits.
46 47
47 48 This class installs various hooks under ``PyOSInputHook`` to handle
48 49 GUI event loop integration.
49 50 """
50 51
51 52 def __init__(self):
52 53 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
53 54 self._apps = {}
54 55 self._reset()
55 56
56 57 def _reset(self):
57 58 self._callback_pyfunctype = None
58 59 self._callback = None
59 60 self._installed = False
60 61 self._current_gui = None
61 62
62 63 def get_pyos_inputhook(self):
63 64 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
64 65 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
65 66
66 67 def get_pyos_inputhook_as_func(self):
67 68 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
68 69 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
69 70
70 71 def set_inputhook(self, callback):
71 72 """Set PyOS_InputHook to callback and return the previous one."""
72 73 self._callback = callback
73 74 self._callback_pyfunctype = self.PYFUNC(callback)
74 75 pyos_inputhook_ptr = self.get_pyos_inputhook()
75 76 original = self.get_pyos_inputhook_as_func()
76 77 pyos_inputhook_ptr.value = \
77 78 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
78 79 self._installed = True
79 80 return original
80 81
81 82 def clear_inputhook(self, app=None):
82 83 """Set PyOS_InputHook to NULL and return the previous one.
83 84
84 85 Parameters
85 86 ----------
86 87 app : optional, ignored
87 88 This parameter is allowed only so that clear_inputhook() can be
88 89 called with a similar interface as all the ``enable_*`` methods. But
89 90 the actual value of the parameter is ignored. This uniform interface
90 91 makes it easier to have user-level entry points in the main IPython
91 92 app like :meth:`enable_gui`."""
92 93 pyos_inputhook_ptr = self.get_pyos_inputhook()
93 94 original = self.get_pyos_inputhook_as_func()
94 95 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
95 96 self._reset()
96 97 return original
97 98
98 99 def clear_app_refs(self, gui=None):
99 100 """Clear IPython's internal reference to an application instance.
100 101
101 102 Whenever we create an app for a user on qt4 or wx, we hold a
102 103 reference to the app. This is needed because in some cases bad things
103 104 can happen if a user doesn't hold a reference themselves. This
104 105 method is provided to clear the references we are holding.
105 106
106 107 Parameters
107 108 ----------
108 109 gui : None or str
109 110 If None, clear all app references. If ('wx', 'qt4') clear
110 111 the app for that toolkit. References are not held for gtk or tk
111 112 as those toolkits don't have the notion of an app.
112 113 """
113 114 if gui is None:
114 115 self._apps = {}
115 116 elif self._apps.has_key(gui):
116 117 del self._apps[gui]
117 118
118 119 def enable_wx(self, app=None):
119 120 """Enable event loop integration with wxPython.
120 121
121 122 Parameters
122 123 ----------
123 124 app : WX Application, optional.
124 125 Running application to use. If not given, we probe WX for an
125 126 existing application object, and create a new one if none is found.
126 127
127 128 Notes
128 129 -----
129 130 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
130 131 the wxPython to integrate with terminal based applications like
131 132 IPython.
132 133
133 134 If ``app`` is not given we probe for an existing one, and return it if
134 135 found. If no existing app is found, we create an :class:`wx.App` as
135 136 follows::
136 137
137 138 import wx
138 139 app = wx.App(redirect=False, clearSigInt=False)
139 140 """
140 141 from IPython.lib.inputhookwx import inputhook_wx
141 142 self.set_inputhook(inputhook_wx)
142 143 self._current_gui = GUI_WX
143 144 import wx
144 145 if app is None:
145 146 app = wx.GetApp()
146 147 if app is None:
147 148 app = wx.App(redirect=False, clearSigInt=False)
148 149 app._in_event_loop = True
149 150 self._apps[GUI_WX] = app
150 151 return app
151 152
152 153 def disable_wx(self):
153 154 """Disable event loop integration with wxPython.
154 155
155 156 This merely sets PyOS_InputHook to NULL.
156 157 """
157 158 if self._apps.has_key(GUI_WX):
158 159 self._apps[GUI_WX]._in_event_loop = False
159 160 self.clear_inputhook()
160 161
161 162 def enable_qt4(self, app=None):
162 163 """Enable event loop integration with PyQt4.
163 164
164 165 Parameters
165 166 ----------
166 167 app : Qt Application, optional.
167 168 Running application to use. If not given, we probe Qt for an
168 169 existing application object, and create a new one if none is found.
169 170
170 171 Notes
171 172 -----
172 173 This methods sets the PyOS_InputHook for PyQt4, which allows
173 174 the PyQt4 to integrate with terminal based applications like
174 175 IPython.
175 176
176 177 If ``app`` is not given we probe for an existing one, and return it if
177 178 found. If no existing app is found, we create an :class:`QApplication`
178 179 as follows::
179 180
180 181 from PyQt4 import QtCore
181 182 app = QtGui.QApplication(sys.argv)
182 183 """
183 184 from IPython.external.qt_for_kernel import QtCore, QtGui
184 185
185 186 if 'pyreadline' in sys.modules:
186 187 # see IPython GitHub Issue #281 for more info on this issue
187 188 # Similar intermittent behavior has been reported on OSX,
188 189 # but not consistently reproducible
189 190 warnings.warn("""PyReadline's inputhook can conflict with Qt, causing delays
190 191 in interactive input. If you do see this issue, we recommend using another GUI
191 192 toolkit if you can, or disable readline with the configuration option
192 193 'TerminalInteractiveShell.readline_use=False', specified in a config file or
193 194 at the command-line""",
194 195 RuntimeWarning)
195 196
196 197 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
197 198 # was set when QtCore was imported, but if it ever got removed,
198 199 # you couldn't reset it. For earlier versions we can
199 200 # probably implement a ctypes version.
200 201 try:
201 202 QtCore.pyqtRestoreInputHook()
202 203 except AttributeError:
203 204 pass
204 205
205 206 self._current_gui = GUI_QT4
206 207 if app is None:
207 208 app = QtCore.QCoreApplication.instance()
208 209 if app is None:
209 210 app = QtGui.QApplication([" "])
210 211 app._in_event_loop = True
211 212 self._apps[GUI_QT4] = app
212 213 return app
213 214
214 215 def disable_qt4(self):
215 216 """Disable event loop integration with PyQt4.
216 217
217 218 This merely sets PyOS_InputHook to NULL.
218 219 """
219 220 if self._apps.has_key(GUI_QT4):
220 221 self._apps[GUI_QT4]._in_event_loop = False
221 222 self.clear_inputhook()
222 223
223 224 def enable_gtk(self, app=None):
224 225 """Enable event loop integration with PyGTK.
225 226
226 227 Parameters
227 228 ----------
228 229 app : ignored
229 230 Ignored, it's only a placeholder to keep the call signature of all
230 231 gui activation methods consistent, which simplifies the logic of
231 232 supporting magics.
232 233
233 234 Notes
234 235 -----
235 236 This methods sets the PyOS_InputHook for PyGTK, which allows
236 237 the PyGTK to integrate with terminal based applications like
237 238 IPython.
238 239 """
239 240 import gtk
240 241 try:
241 242 gtk.set_interactive(True)
242 243 self._current_gui = GUI_GTK
243 244 except AttributeError:
244 245 # For older versions of gtk, use our own ctypes version
245 246 from IPython.lib.inputhookgtk import inputhook_gtk
246 247 self.set_inputhook(inputhook_gtk)
247 248 self._current_gui = GUI_GTK
248 249
249 250 def disable_gtk(self):
250 251 """Disable event loop integration with PyGTK.
251 252
252 253 This merely sets PyOS_InputHook to NULL.
253 254 """
254 255 self.clear_inputhook()
255 256
256 257 def enable_tk(self, app=None):
257 258 """Enable event loop integration with Tk.
258 259
259 260 Parameters
260 261 ----------
261 262 app : toplevel :class:`Tkinter.Tk` widget, optional.
262 263 Running toplevel widget to use. If not given, we probe Tk for an
263 264 existing one, and create a new one if none is found.
264 265
265 266 Notes
266 267 -----
267 268 If you have already created a :class:`Tkinter.Tk` object, the only
268 269 thing done by this method is to register with the
269 270 :class:`InputHookManager`, since creating that object automatically
270 271 sets ``PyOS_InputHook``.
271 272 """
272 273 self._current_gui = GUI_TK
273 274 if app is None:
274 275 import Tkinter
275 276 app = Tkinter.Tk()
276 277 app.withdraw()
277 278 self._apps[GUI_TK] = app
278 279 return app
279 280
280 281 def disable_tk(self):
281 282 """Disable event loop integration with Tkinter.
282 283
283 284 This merely sets PyOS_InputHook to NULL.
284 285 """
285 286 self.clear_inputhook()
286 287
287 288
289 <<<<<<< HEAD
288 290
289 291 def enable_pyglet(self, app=None):
290 292 """Enable event loop integration with pyglet.
293 =======
294 def enable_glut(self, app=None):
295 """Enable event loop integration with GLUT.
296 >>>>>>> Added code for the GLUT interactive session
291 297
292 298 Parameters
293 299 ----------
294 300 app : ignored
295 301 Ignored, it's only a placeholder to keep the call signature of all
296 302 gui activation methods consistent, which simplifies the logic of
297 303 supporting magics.
298 304
299 305 Notes
300 306 -----
307 <<<<<<< HEAD
301 308 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
302 309 pyglet to integrate with terminal based applications like
303 310 IPython.
304 311
305 312 """
306 313 import pyglet
307 314 from IPython.lib.inputhookpyglet import inputhook_pyglet
308 315 self.set_inputhook(inputhook_pyglet)
309 316 self._current_gui = GUI_PYGLET
310 317 return app
311 318
312 319 def disable_pyglet(self):
313 320 """Disable event loop integration with pyglet.
314 321
315 322 This merely sets PyOS_InputHook to NULL.
316 323 """
324 =======
325 This methods sets the PyOS_InputHook for GLUT, which allows
326 the GLUT to integrate with terminal based applications like
327 IPython.
328
329 GLUT is a quite old library and it is difficult to ensure proper
330 integration within IPython since original GLUT does not allow to handle
331 events one by one. Instead, it requires for the mainloop to be entered
332 and never returned (there is not event a function to exit he
333 mainloop). Fortunately, there are alternatives such as freeglut
334 (avaialble for linux and windows) and the OSX implementation gives
335 access to a glutCheckLoop() function that blocks itself until a new
336 event is received. This means we have to setup a default timer to
337 ensure we got at least one event that will unblock the function.
338
339 Furthermore, it is not possible to install these handlers wihtout a
340 window being first created. We choose to make this window visible for
341 the user to realize that it does not need to create a new one (or this
342 will bring troubles). But, display mode options are then set here and
343 it won't be possible for the user to change them without modifying the
344 code or this has to be made availble via IPython options system.
345
346 Script integration
347 ------------------
348
349 ::
350
351 interactive = False
352 if glut.glutGetWindow() > 0:
353 interactive = True
354 else:
355 glut.glutInit(sys.argv)
356 glut.glutInitDisplayMode( glut.GLUT_DOUBLE |
357 glut.GLUT_RGBA |
358 glut.GLUT_DEPTH )
359 ...
360 if not interactive:
361 glut.glutMainLoop()
362 """
363 import OpenGL.GLUT as glut
364 import OpenGL.platform as platform
365
366 def timer_none(fps):
367 ''' Dummy timer function '''
368 pass
369
370 def display():
371 ''' Dummy display function '''
372 pass
373
374 def timer(fps):
375 # We should normally set the active window to 1 and post a
376 # redisplay for each window. The problem is that we do not know
377 # how much active windows we have and there is no function in glut
378 # to get that number.
379 # glut.glutSetWindow(1)
380 glut.glutTimerFunc( int(1000.0/fps), timer, fps)
381 glut.glutPostRedisplay()
382
383 glutMainLoopEvent = None
384 if sys.platform == 'darwin':
385 try:
386 glutCheckLoop = platform.createBaseFunction(
387 'glutCheckLoop', dll=platform.GLUT, resultType=None,
388 argTypes=[],
389 doc='glutCheckLoop( ) -> None',
390 argNames=(),
391 )
392 except AttributeError:
393 raise RuntimeError,\
394 'Your glut implementation does not allow interactive sessions' \
395 'Consider installing freeglut.'
396 glutMainLoopEvent = glutCheckLoop
397 elif glut.HAVE_FREEGLUT:
398 glutMainLoopEvent = glut.glutMainLoopEvent
399 else:
400 raise RuntimeError,\
401 'Your glut implementation does not allow interactive sessions. ' \
402 'Consider installing freeglut.'
403
404 def inputhook_glut():
405 """ Process pending GLUT events only. """
406 # We need to protect against a user pressing Control-C when IPython is
407 # idle and this is running. We trap KeyboardInterrupt and pass.
408 try:
409 glutMainLoopEvent()
410 except KeyboardInterrupt:
411 pass
412 return 0
413
414 # Frame per second : 60
415 # Should be probably an IPython option
416 fps = 60
417 if not self._apps.has_key(GUI_GLUT):
418 glut.glutInit(sys.argv)
419
420 # Display mode shoudl be also an Ipython option since user won't be able
421 # to change it later
422 glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH)
423 glut.glutCreateWindow(sys.argv[0])
424 glut.glutReshapeWindow(1,1)
425 glut.glutHideWindow()
426 glut.glutDisplayFunc(display)
427 glut.glutTimerFunc( int(1000.0/fps), timer, fps)
428 else:
429 glut.glutDisplayFunc(display)
430 glut.glutTimerFunc( int(1000.0/fps), timer, fps)
431
432 self.set_inputhook(inputhook_glut)
433 self._current_gui = GUI_GLUT
434 self._apps[GUI_GLUT] = True
435
436
437 def disable_glut(self):
438 """Disable event loop integration with glut.
439
440 This sets PyOS_InputHook to NULL and set the display function to a
441 dummy one and set the timer to a dummy timer that will be triggered
442 very far in the future.
443 """
444 glut.HideWindow()
445 glut.glutTimerFunc( sys.maxint-1, null_timer_none, 0)
446 >>>>>>> Added code for the GLUT interactive session
317 447 self.clear_inputhook()
318 448
319 449
320 450 def current_gui(self):
321 451 """Return a string indicating the currently active GUI or None."""
322 452 return self._current_gui
323 453
324 454 inputhook_manager = InputHookManager()
325 455
326 456 enable_wx = inputhook_manager.enable_wx
327 457 disable_wx = inputhook_manager.disable_wx
328 458 enable_qt4 = inputhook_manager.enable_qt4
329 459 disable_qt4 = inputhook_manager.disable_qt4
330 460 enable_gtk = inputhook_manager.enable_gtk
331 461 disable_gtk = inputhook_manager.disable_gtk
332 462 enable_tk = inputhook_manager.enable_tk
333 463 disable_tk = inputhook_manager.disable_tk
464 <<<<<<< HEAD
334 465 enable_pyglet = inputhook_manager.enable_pyglet
335 466 disable_pyglet = inputhook_manager.disable_pyglet
467 =======
468 enable_glut = inputhook_manager.enable_glut
469 disable_glut = inputhook_manager.disable_glut
470 >>>>>>> Added code for the GLUT interactive session
336 471 clear_inputhook = inputhook_manager.clear_inputhook
337 472 set_inputhook = inputhook_manager.set_inputhook
338 473 current_gui = inputhook_manager.current_gui
339 474 clear_app_refs = inputhook_manager.clear_app_refs
340 475
341 476
342 477 # Convenience function to switch amongst them
343 478 def enable_gui(gui=None, app=None):
344 479 """Switch amongst GUI input hooks by name.
345 480
346 481 This is just a utility wrapper around the methods of the InputHookManager
347 482 object.
348 483
349 484 Parameters
350 485 ----------
351 486 gui : optional, string or None
352 487 If None, clears input hook, otherwise it must be one of the recognized
353 488 GUI names (see ``GUI_*`` constants in module).
354 489
355 490 app : optional, existing application object.
356 491 For toolkits that have the concept of a global app, you can supply an
357 492 existing one. If not given, the toolkit will be probed for one, and if
358 493 none is found, a new one will be created. Note that GTK does not have
359 494 this concept, and passing an app if `gui`=="GTK" will raise an error.
360 495
361 496 Returns
362 497 -------
363 498 The output of the underlying gui switch routine, typically the actual
364 499 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
365 500 one.
366 501 """
367 502 guis = {None: clear_inputhook,
368 503 GUI_OSX: lambda app=False: None,
369 504 GUI_TK: enable_tk,
370 505 GUI_GTK: enable_gtk,
371 506 GUI_WX: enable_wx,
372 507 GUI_QT: enable_qt4, # qt3 not supported
373 508 GUI_QT4: enable_qt4,
509 <<<<<<< HEAD
374 510 GUI_PYGLET: enable_pyglet,
375 511 }
512 =======
513 GUI_GLUT: enable_glut}
514 >>>>>>> Added code for the GLUT interactive session
376 515 try:
377 516 gui_hook = guis[gui]
378 517 except KeyError:
379 518 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
380 519 raise ValueError(e)
381 520 return gui_hook(app)
382 521
General Comments 0
You need to be logged in to leave comments. Login now