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