##// END OF EJS Templates
Refactor inputhook to allow easy extension.
Thomas Kluyver -
Show More
@@ -110,7 +110,9 b' class InputHookManager(object):'
110 110 warn("IPython GUI event loop requires ctypes, %gui will not be available")
111 111 return
112 112 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
113 self._apps = {}
113 self.guihooks = {}
114 self.aliases = {}
115 self.apps = {}
114 116 self._reset()
115 117
116 118 def _reset(self):
@@ -177,11 +179,105 b' class InputHookManager(object):'
177 179 as those toolkits don't have the notion of an app.
178 180 """
179 181 if gui is None:
180 self._apps = {}
181 elif gui in self._apps:
182 del self._apps[gui]
182 self.apps = {}
183 elif gui in self.apps:
184 del self.apps[gui]
183 185
184 def enable_wx(self, app=None):
186 def register(self, toolkitname, *aliases):
187 """Register a class to provide the event loop for a given GUI.
188
189 This is intended to be used as a class decorator. It should be passed
190 the names with which to register this GUI integration. The classes
191 themselves should subclass :class:`InputHookBase`.
192
193 Examples
194 --------
195
196 @inputhook_manager.register('qt')
197 class QtInputHook(InputHookBase):
198 def enable(self, app=None):
199 ...
200 """
201 def decorator(cls):
202 inst = cls(self)
203 self.guihooks[toolkitname] = inst
204 for a in aliases:
205 self.aliases[a] = toolkitname
206 return cls
207 return decorator
208
209 def current_gui(self):
210 """Return a string indicating the currently active GUI or None."""
211 return self._current_gui
212
213 def enable_gui(self, gui=None, app=None):
214 """Switch amongst GUI input hooks by name.
215
216 This is a higher level method than :meth:`set_inputhook` - it uses the
217 GUI name to look up a registered object which enables the input hook
218 for that GUI.
219
220 Parameters
221 ----------
222 gui : optional, string or None
223 If None (or 'none'), clears input hook, otherwise it must be one
224 of the recognized GUI names (see ``GUI_*`` constants in module).
225
226 app : optional, existing application object.
227 For toolkits that have the concept of a global app, you can supply an
228 existing one. If not given, the toolkit will be probed for one, and if
229 none is found, a new one will be created. Note that GTK does not have
230 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
231
232 Returns
233 -------
234 The output of the underlying gui switch routine, typically the actual
235 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
236 one.
237 """
238 if gui in (None, GUI_NONE):
239 return self.disable_gui()
240
241 if gui in self.aliases:
242 return self.enable_gui(self.aliases[gui], app)
243
244 try:
245 gui_hook = self.guihooks[gui]
246 except KeyError:
247 e = "Invalid GUI request {!r}, valid ones are: {}"
248 raise ValueError(e.format(gui, ', '.join(self.guihooks)))
249 self._current_gui = gui
250 return gui_hook.enable(app)
251
252 def disable_gui(self):
253 """Disable GUI event loop integration.
254
255 If an application was registered, this sets its ``_in_event_loop``
256 attribute to False. It then calls :meth:`clear_inputhook`.
257 """
258 gui = self._current_gui
259 if gui in self.apps:
260 self.apps[gui]._in_event_loop = False
261 return self.clear_inputhook()
262
263 class InputHookBase(object):
264 """Base class for input hooks for specific toolkits.
265
266 Subclasses should define an :meth:`enable` method with one argument, ``app``,
267 which will either be an instance of the toolkit's application class, or None.
268 They may also define a :meth:`disable` method with no arguments.
269 """
270 def __init__(self, manager):
271 self.manager = manager
272
273 def disable(self):
274 pass
275
276 inputhook_manager = InputHookManager()
277
278 @inputhook_manager.register('wx')
279 class WxInputHook(InputHookBase):
280 def enable(self, app=None):
185 281 """Enable event loop integration with wxPython.
186 282
187 283 Parameters
@@ -212,30 +308,29 b' class InputHookManager(object):'
212 308
213 309 from IPython.lib.inputhookwx import inputhook_wx
214 310 from IPython.external.appnope import nope
215 self.set_inputhook(inputhook_wx)
311 self.manager.set_inputhook(inputhook_wx)
216 312 nope()
217 self._current_gui = GUI_WX
313
218 314 import wx
219 315 if app is None:
220 316 app = wx.GetApp()
221 317 if app is None:
222 318 app = wx.App(redirect=False, clearSigInt=False)
223 319 app._in_event_loop = True
224 self._apps[GUI_WX] = app
320 self.manager.apps[GUI_WX] = app
225 321 return app
226 322
227 def disable_wx(self):
323 def disable(self):
228 324 """Disable event loop integration with wxPython.
229 325
230 This merely sets PyOS_InputHook to NULL.
326 This restores appnapp on OS X
231 327 """
232 328 from IPython.external.appnope import nap
233 if GUI_WX in self._apps:
234 self._apps[GUI_WX]._in_event_loop = False
235 self.clear_inputhook()
236 329 nap()
237 330
238 def enable_qt4(self, app=None):
331 @inputhook_manager.register('qt', 'qt4')
332 class Qt4InputHook(InputHookBase):
333 def enable(self, app=None):
239 334 """Enable event loop integration with PyQt4.
240 335
241 336 Parameters
@@ -260,26 +355,24 b' class InputHookManager(object):'
260 355 from IPython.lib.inputhookqt4 import create_inputhook_qt4
261 356 from IPython.external.appnope import nope
262 357 app, inputhook_qt4 = create_inputhook_qt4(self, app)
263 self.set_inputhook(inputhook_qt4)
358 self.manager.set_inputhook(inputhook_qt4)
264 359 nope()
265 360
266 self._current_gui = GUI_QT4
267 361 app._in_event_loop = True
268 self._apps[GUI_QT4] = app
362 self.manager.apps[GUI_QT4] = app
269 363 return app
270 364
271 365 def disable_qt4(self):
272 366 """Disable event loop integration with PyQt4.
273 367
274 This merely sets PyOS_InputHook to NULL.
368 This restores appnapp on OS X
275 369 """
276 370 from IPython.external.appnope import nap
277 if GUI_QT4 in self._apps:
278 self._apps[GUI_QT4]._in_event_loop = False
279 self.clear_inputhook()
280 371 nap()
281 372
282 def enable_gtk(self, app=None):
373 @inputhook_manager.register('gtk')
374 class GtkInputHook(InputHookBase):
375 def enable(self, app=None):
283 376 """Enable event loop integration with PyGTK.
284 377
285 378 Parameters
@@ -298,21 +391,15 b' class InputHookManager(object):'
298 391 import gtk
299 392 try:
300 393 gtk.set_interactive(True)
301 self._current_gui = GUI_GTK
302 394 except AttributeError:
303 395 # For older versions of gtk, use our own ctypes version
304 396 from IPython.lib.inputhookgtk import inputhook_gtk
305 self.set_inputhook(inputhook_gtk)
306 self._current_gui = GUI_GTK
397 self.manager.set_inputhook(inputhook_gtk)
307 398
308 def disable_gtk(self):
309 """Disable event loop integration with PyGTK.
310
311 This merely sets PyOS_InputHook to NULL.
312 """
313 self.clear_inputhook()
314 399
315 def enable_tk(self, app=None):
400 @inputhook_manager.register('tk')
401 class TkInputHook(InputHookBase):
402 def enable(self, app=None):
316 403 """Enable event loop integration with Tk.
317 404
318 405 Parameters
@@ -328,7 +415,6 b' class InputHookManager(object):'
328 415 :class:`InputHookManager`, since creating that object automatically
329 416 sets ``PyOS_InputHook``.
330 417 """
331 self._current_gui = GUI_TK
332 418 if app is None:
333 419 try:
334 420 from tkinter import Tk # Py 3
@@ -336,19 +422,14 b' class InputHookManager(object):'
336 422 from Tkinter import Tk # Py 2
337 423 app = Tk()
338 424 app.withdraw()
339 self._apps[GUI_TK] = app
425 self.manager.apps[GUI_TK] = app
340 426 return app
341 427
342 def disable_tk(self):
343 """Disable event loop integration with Tkinter.
344
345 This merely sets PyOS_InputHook to NULL.
346 """
347 self.clear_inputhook()
348
349 428
350 def enable_glut(self, app=None):
351 """ Enable event loop integration with GLUT.
429 @inputhook_manager.register('glut')
430 class GlutInputHook(InputHookBase):
431 def enable(self, app=None):
432 """Enable event loop integration with GLUT.
352 433
353 434 Parameters
354 435 ----------
@@ -377,7 +458,7 b' class InputHookManager(object):'
377 458 glut_close, glut_display, \
378 459 glut_idle, inputhook_glut
379 460
380 if GUI_GLUT not in self._apps:
461 if GUI_GLUT not in self.manager.apps:
381 462 glut.glutInit( sys.argv )
382 463 glut.glutInitDisplayMode( glut_display_mode )
383 464 # This is specific to freeglut
@@ -394,12 +475,11 b' class InputHookManager(object):'
394 475 glut.glutWMCloseFunc( glut_close )
395 476 glut.glutDisplayFunc( glut_display )
396 477 glut.glutIdleFunc( glut_idle)
397 self.set_inputhook( inputhook_glut )
398 self._current_gui = GUI_GLUT
399 self._apps[GUI_GLUT] = True
478 self.manager.set_inputhook( inputhook_glut )
479 self.manager.apps[GUI_GLUT] = True
400 480
401 481
402 def disable_glut(self):
482 def disable(self):
403 483 """Disable event loop integration with glut.
404 484
405 485 This sets PyOS_InputHook to NULL and set the display function to a
@@ -411,9 +491,11 b' class InputHookManager(object):'
411 491
412 492 glut.glutHideWindow() # This is an event to be processed below
413 493 glutMainLoopEvent()
414 self.clear_inputhook()
494 super(GlutInputHook, self).disable()
415 495
416 def enable_pyglet(self, app=None):
496 @inputhook_manager.register('pyglet')
497 class PygletInputHook(InputHookBase):
498 def enable(self, app=None):
417 499 """Enable event loop integration with pyglet.
418 500
419 501 Parameters
@@ -431,18 +513,13 b' class InputHookManager(object):'
431 513
432 514 """
433 515 from IPython.lib.inputhookpyglet import inputhook_pyglet
434 self.set_inputhook(inputhook_pyglet)
435 self._current_gui = GUI_PYGLET
516 self.manager.set_inputhook(inputhook_pyglet)
436 517 return app
437 518
438 def disable_pyglet(self):
439 """Disable event loop integration with pyglet.
440
441 This merely sets PyOS_InputHook to NULL.
442 """
443 self.clear_inputhook()
444 519
445 def enable_gtk3(self, app=None):
520 @inputhook_manager.register('gtk3')
521 class Gtk3InputHook(InputHookBase):
522 def enable(self, app=None):
446 523 """Enable event loop integration with Gtk3 (gir bindings).
447 524
448 525 Parameters
@@ -459,84 +536,25 b' class InputHookManager(object):'
459 536 IPython.
460 537 """
461 538 from IPython.lib.inputhookgtk3 import inputhook_gtk3
462 self.set_inputhook(inputhook_gtk3)
463 self._current_gui = GUI_GTK
539 self.manager.set_inputhook(inputhook_gtk3)
540 self.manager._current_gui = GUI_GTK
464 541
465 def disable_gtk3(self):
466 """Disable event loop integration with PyGTK.
467 542
468 This merely sets PyOS_InputHook to NULL.
469 """
470 self.clear_inputhook()
471
472 def current_gui(self):
473 """Return a string indicating the currently active GUI or None."""
474 return self._current_gui
475
476 inputhook_manager = InputHookManager()
477
478 enable_wx = inputhook_manager.enable_wx
479 disable_wx = inputhook_manager.disable_wx
480 enable_qt4 = inputhook_manager.enable_qt4
481 disable_qt4 = inputhook_manager.disable_qt4
482 enable_gtk = inputhook_manager.enable_gtk
483 disable_gtk = inputhook_manager.disable_gtk
484 enable_tk = inputhook_manager.enable_tk
485 disable_tk = inputhook_manager.disable_tk
486 enable_glut = inputhook_manager.enable_glut
487 disable_glut = inputhook_manager.disable_glut
488 enable_pyglet = inputhook_manager.enable_pyglet
489 disable_pyglet = inputhook_manager.disable_pyglet
490 enable_gtk3 = inputhook_manager.enable_gtk3
491 disable_gtk3 = inputhook_manager.disable_gtk3
492 543 clear_inputhook = inputhook_manager.clear_inputhook
493 544 set_inputhook = inputhook_manager.set_inputhook
494 545 current_gui = inputhook_manager.current_gui
495 546 clear_app_refs = inputhook_manager.clear_app_refs
496
497 guis = {None: clear_inputhook,
498 GUI_NONE: clear_inputhook,
499 GUI_OSX: lambda app=False: None,
500 GUI_TK: enable_tk,
501 GUI_GTK: enable_gtk,
502 GUI_WX: enable_wx,
503 GUI_QT: enable_qt4, # qt3 not supported
504 GUI_QT4: enable_qt4,
505 GUI_GLUT: enable_glut,
506 GUI_PYGLET: enable_pyglet,
507 GUI_GTK3: enable_gtk3,
508 }
509
510
511 # Convenience function to switch amongst them
512 def enable_gui(gui=None, app=None):
513 """Switch amongst GUI input hooks by name.
514
515 This is just a utility wrapper around the methods of the InputHookManager
516 object.
517
518 Parameters
519 ----------
520 gui : optional, string or None
521 If None (or 'none'), clears input hook, otherwise it must be one
522 of the recognized GUI names (see ``GUI_*`` constants in module).
523
524 app : optional, existing application object.
525 For toolkits that have the concept of a global app, you can supply an
526 existing one. If not given, the toolkit will be probed for one, and if
527 none is found, a new one will be created. Note that GTK does not have
528 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
529
530 Returns
531 -------
532 The output of the underlying gui switch routine, typically the actual
533 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
534 one.
535 """
536 try:
537 gui_hook = guis[gui]
538 except KeyError:
539 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
540 raise ValueError(e)
541 return gui_hook(app)
542
547 enable_gui = inputhook_manager.enable_gui
548 disable_gui = inputhook_manager.disable_gui
549 guis = inputhook_manager.guihooks
550
551 # Deprecated methods: kept for backwards compatibility, do not use in new code
552 enable_wx = lambda app=None: inputhook_manager.enable_gui('wx', app)
553 enable_qt4 = lambda app=None: inputhook_manager.enable_gui('qt4', app)
554 enable_gtk = lambda app=None: inputhook_manager.enable_gui('gtk', app)
555 enable_tk = lambda app=None: inputhook_manager.enable_gui('tk', app)
556 enable_glut = lambda app=None: inputhook_manager.enable_gui('glut', app)
557 enable_pyglet = lambda app=None: inputhook_manager.enable_gui('pyglet', app)
558 enable_gtk3 = lambda app=None: inputhook_manager.enable_gui('gtk3', app)
559 disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \
560 disable_pyglet = inputhook_manager.disable_gui No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now