1# coding: utf-8 2""" 3Inputhook management for GUI event loop integration. 4""" 5 6#----------------------------------------------------------------------------- 7# Copyright (C) 2008-2011 The IPython Development Team 8# 9# Distributed under the terms of the BSD License. The full license is in 10# the file COPYING, distributed as part of this software. 11#----------------------------------------------------------------------------- 12 13#----------------------------------------------------------------------------- 14# Imports 15#----------------------------------------------------------------------------- 16 17import sys 18import select 19 20#----------------------------------------------------------------------------- 21# Constants 22#----------------------------------------------------------------------------- 23 24# Constants for identifying the GUI toolkits. 25GUI_WX = 'wx' 26GUI_QT = 'qt' 27GUI_QT4 = 'qt4' 28GUI_QT5 = 'qt5' 29GUI_GTK = 'gtk' 30GUI_TK = 'tk' 31GUI_OSX = 'osx' 32GUI_GLUT = 'glut' 33GUI_PYGLET = 'pyglet' 34GUI_GTK3 = 'gtk3' 35GUI_NONE = 'none' # i.e. disable 36 37#----------------------------------------------------------------------------- 38# Utilities 39#----------------------------------------------------------------------------- 40 41def ignore_CTRL_C(): 42 """Ignore CTRL+C (not implemented).""" 43 pass 44 45def allow_CTRL_C(): 46 """Take CTRL+C into account (not implemented).""" 47 pass 48 49#----------------------------------------------------------------------------- 50# Main InputHookManager class 51#----------------------------------------------------------------------------- 52 53 54class InputHookManager(object): 55 """Manage PyOS_InputHook for different GUI toolkits. 56 57 This class installs various hooks under ``PyOSInputHook`` to handle 58 GUI event loop integration. 59 """ 60 61 def __init__(self): 62 self._return_control_callback = None 63 self._apps = {} 64 self._reset() 65 self.pyplot_imported = False 66 67 def _reset(self): 68 self._callback_pyfunctype = None 69 self._callback = None 70 self._current_gui = None 71 72 def set_return_control_callback(self, return_control_callback): 73 self._return_control_callback = return_control_callback 74 75 def get_return_control_callback(self): 76 return self._return_control_callback 77 78 def return_control(self): 79 return self._return_control_callback() 80 81 def get_inputhook(self): 82 return self._callback 83 84 def set_inputhook(self, callback): 85 """Set inputhook to callback.""" 86 # We don't (in the context of PyDev console) actually set PyOS_InputHook, but rather 87 # while waiting for input on xmlrpc we run this code 88 self._callback = callback 89 90 def clear_inputhook(self, app=None): 91 """Clear input hook. 92 93 Parameters 94 ---------- 95 app : optional, ignored 96 This parameter is allowed only so that clear_inputhook() can be 97 called with a similar interface as all the ``enable_*`` methods. But 98 the actual value of the parameter is ignored. This uniform interface 99 makes it easier to have user-level entry points in the main IPython 100 app like :meth:`enable_gui`.""" 101 self._reset() 102 103 def clear_app_refs(self, gui=None): 104 """Clear IPython's internal reference to an application instance. 105 106 Whenever we create an app for a user on qt4 or wx, we hold a 107 reference to the app. This is needed because in some cases bad things 108 can happen if a user doesn't hold a reference themselves. This 109 method is provided to clear the references we are holding. 110 111 Parameters 112 ---------- 113 gui : None or str 114 If None, clear all app references. If ('wx', 'qt4') clear 115 the app for that toolkit. References are not held for gtk or tk 116 as those toolkits don't have the notion of an app. 117 """ 118 if gui is None: 119 self._apps = {} 120 elif gui in self._apps: 121 del self._apps[gui] 122 123 def enable_wx(self, app=None): 124 """Enable event loop integration with wxPython. 125 126 Parameters 127 ---------- 128 app : WX Application, optional. 129 Running application to use. If not given, we probe WX for an 130 existing application object, and create a new one if none is found. 131 132 Notes 133 ----- 134 This methods sets the ``PyOS_InputHook`` for wxPython, which allows 135 the wxPython to integrate with terminal based applications like 136 IPython. 137 138 If ``app`` is not given we probe for an existing one, and return it if 139 found. If no existing app is found, we create an :class:`wx.App` as 140 follows:: 141 142 import wx 143 app = wx.App(redirect=False, clearSigInt=False) 144 """ 145 import wx 146 from distutils.version import LooseVersion as V 147 wx_version = V(wx.__version__).version # @UndefinedVariable 148 149 if wx_version < [2, 8]: 150 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__) # @UndefinedVariable 151 152 from pydev_ipython.inputhookwx import inputhook_wx 153 self.set_inputhook(inputhook_wx) 154 self._current_gui = GUI_WX 155 156 if app is None: 157 app = wx.GetApp() # @UndefinedVariable 158 if app is None: 159 app = wx.App(redirect=False, clearSigInt=False) # @UndefinedVariable 160 app._in_event_loop = True 161 self._apps[GUI_WX] = app 162 return app 163 164 def disable_wx(self): 165 """Disable event loop integration with wxPython. 166 167 This merely sets PyOS_InputHook to NULL. 168 """ 169 if GUI_WX in self._apps: 170 self._apps[GUI_WX]._in_event_loop = False 171 self.clear_inputhook() 172 173 def enable_qt(self, app=None): 174 from pydev_ipython.qt_for_kernel import QT_API, QT_API_PYQT5 175 if QT_API == QT_API_PYQT5: 176 self.enable_qt5(app) 177 else: 178 self.enable_qt4(app) 179 180 def enable_qt4(self, app=None): 181 """Enable event loop integration with PyQt4. 182 183 Parameters 184 ---------- 185 app : Qt Application, optional. 186 Running application to use. If not given, we probe Qt for an 187 existing application object, and create a new one if none is found. 188 189 Notes 190 ----- 191 This methods sets the PyOS_InputHook for PyQt4, which allows 192 the PyQt4 to integrate with terminal based applications like 193 IPython. 194 195 If ``app`` is not given we probe for an existing one, and return it if 196 found. If no existing app is found, we create an :class:`QApplication` 197 as follows:: 198 199 from PyQt4 import QtCore 200 app = QtGui.QApplication(sys.argv) 201 """ 202 from pydev_ipython.inputhookqt4 import create_inputhook_qt4 203 app, inputhook_qt4 = create_inputhook_qt4(self, app) 204 self.set_inputhook(inputhook_qt4) 205 206 self._current_gui = GUI_QT4 207 app._in_event_loop = True 208 self._apps[GUI_QT4] = app 209 return app 210 211 def disable_qt4(self): 212 """Disable event loop integration with PyQt4. 213 214 This merely sets PyOS_InputHook to NULL. 215 """ 216 if GUI_QT4 in self._apps: 217 self._apps[GUI_QT4]._in_event_loop = False 218 self.clear_inputhook() 219 220 def enable_qt5(self, app=None): 221 from pydev_ipython.inputhookqt5 import create_inputhook_qt5 222 app, inputhook_qt5 = create_inputhook_qt5(self, app) 223 self.set_inputhook(inputhook_qt5) 224 225 self._current_gui = GUI_QT5 226 app._in_event_loop = True 227 self._apps[GUI_QT5] = app 228 return app 229 230 def disable_qt5(self): 231 if GUI_QT5 in self._apps: 232 self._apps[GUI_QT5]._in_event_loop = False 233 self.clear_inputhook() 234 235 def enable_gtk(self, app=None): 236 """Enable event loop integration with PyGTK. 237 238 Parameters 239 ---------- 240 app : ignored 241 Ignored, it's only a placeholder to keep the call signature of all 242 gui activation methods consistent, which simplifies the logic of 243 supporting magics. 244 245 Notes 246 ----- 247 This methods sets the PyOS_InputHook for PyGTK, which allows 248 the PyGTK to integrate with terminal based applications like 249 IPython. 250 """ 251 from pydev_ipython.inputhookgtk import create_inputhook_gtk 252 self.set_inputhook(create_inputhook_gtk(self._stdin_file)) 253 self._current_gui = GUI_GTK 254 255 def disable_gtk(self): 256 """Disable event loop integration with PyGTK. 257 258 This merely sets PyOS_InputHook to NULL. 259 """ 260 self.clear_inputhook() 261 262 def enable_tk(self, app=None): 263 """Enable event loop integration with Tk. 264 265 Parameters 266 ---------- 267 app : toplevel :class:`Tkinter.Tk` widget, optional. 268 Running toplevel widget to use. If not given, we probe Tk for an 269 existing one, and create a new one if none is found. 270 271 Notes 272 ----- 273 If you have already created a :class:`Tkinter.Tk` object, the only 274 thing done by this method is to register with the 275 :class:`InputHookManager`, since creating that object automatically 276 sets ``PyOS_InputHook``. 277 """ 278 self._current_gui = GUI_TK 279 if app is None: 280 try: 281 import Tkinter as _TK 282 except: 283 # Python 3 284 import tkinter as _TK # @UnresolvedImport 285 app = _TK.Tk() 286 app.withdraw() 287 self._apps[GUI_TK] = app 288 289 from pydev_ipython.inputhooktk import create_inputhook_tk 290 self.set_inputhook(create_inputhook_tk(app)) 291 return app 292 293 def disable_tk(self): 294 """Disable event loop integration with Tkinter. 295 296 This merely sets PyOS_InputHook to NULL. 297 """ 298 self.clear_inputhook() 299 300 301 def enable_glut(self, app=None): 302 """ Enable event loop integration with GLUT. 303 304 Parameters 305 ---------- 306 307 app : ignored 308 Ignored, it's only a placeholder to keep the call signature of all 309 gui activation methods consistent, which simplifies the logic of 310 supporting magics. 311 312 Notes 313 ----- 314 315 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to 316 integrate with terminal based applications like IPython. Due to GLUT 317 limitations, it is currently not possible to start the event loop 318 without first creating a window. You should thus not create another 319 window but use instead the created one. See 'gui-glut.py' in the 320 docs/examples/lib directory. 321 322 The default screen mode is set to: 323 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH 324 """ 325 326 import OpenGL.GLUT as glut # @UnresolvedImport 327 from pydev_ipython.inputhookglut import glut_display_mode, \ 328 glut_close, glut_display, \ 329 glut_idle, inputhook_glut 330 331 if GUI_GLUT not in self._apps: 332 glut.glutInit(sys.argv) 333 glut.glutInitDisplayMode(glut_display_mode) 334 # This is specific to freeglut 335 if bool(glut.glutSetOption): 336 glut.glutSetOption(glut.GLUT_ACTION_ON_WINDOW_CLOSE, 337 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS) 338 glut.glutCreateWindow(sys.argv[0]) 339 glut.glutReshapeWindow(1, 1) 340 glut.glutHideWindow() 341 glut.glutWMCloseFunc(glut_close) 342 glut.glutDisplayFunc(glut_display) 343 glut.glutIdleFunc(glut_idle) 344 else: 345 glut.glutWMCloseFunc(glut_close) 346 glut.glutDisplayFunc(glut_display) 347 glut.glutIdleFunc(glut_idle) 348 self.set_inputhook(inputhook_glut) 349 self._current_gui = GUI_GLUT 350 self._apps[GUI_GLUT] = True 351 352 353 def disable_glut(self): 354 """Disable event loop integration with glut. 355 356 This sets PyOS_InputHook to NULL and set the display function to a 357 dummy one and set the timer to a dummy timer that will be triggered 358 very far in the future. 359 """ 360 import OpenGL.GLUT as glut # @UnresolvedImport 361 from glut_support import glutMainLoopEvent # @UnresolvedImport 362 363 glut.glutHideWindow() # This is an event to be processed below 364 glutMainLoopEvent() 365 self.clear_inputhook() 366 367 def enable_pyglet(self, app=None): 368 """Enable event loop integration with pyglet. 369 370 Parameters 371 ---------- 372 app : ignored 373 Ignored, it's only a placeholder to keep the call signature of all 374 gui activation methods consistent, which simplifies the logic of 375 supporting magics. 376 377 Notes 378 ----- 379 This methods sets the ``PyOS_InputHook`` for pyglet, which allows 380 pyglet to integrate with terminal based applications like 381 IPython. 382 383 """ 384 from pydev_ipython.inputhookpyglet import inputhook_pyglet 385 self.set_inputhook(inputhook_pyglet) 386 self._current_gui = GUI_PYGLET 387 return app 388 389 def disable_pyglet(self): 390 """Disable event loop integration with pyglet. 391 392 This merely sets PyOS_InputHook to NULL. 393 """ 394 self.clear_inputhook() 395 396 def enable_gtk3(self, app=None): 397 """Enable event loop integration with Gtk3 (gir bindings). 398 399 Parameters 400 ---------- 401 app : ignored 402 Ignored, it's only a placeholder to keep the call signature of all 403 gui activation methods consistent, which simplifies the logic of 404 supporting magics. 405 406 Notes 407 ----- 408 This methods sets the PyOS_InputHook for Gtk3, which allows 409 the Gtk3 to integrate with terminal based applications like 410 IPython. 411 """ 412 from pydev_ipython.inputhookgtk3 import create_inputhook_gtk3 413 self.set_inputhook(create_inputhook_gtk3(self._stdin_file)) 414 self._current_gui = GUI_GTK 415 416 def disable_gtk3(self): 417 """Disable event loop integration with PyGTK. 418 419 This merely sets PyOS_InputHook to NULL. 420 """ 421 self.clear_inputhook() 422 423 def enable_mac(self, app=None): 424 """ Enable event loop integration with MacOSX. 425 426 We call function pyplot.pause, which updates and displays active 427 figure during pause. It's not MacOSX-specific, but it enables to 428 avoid inputhooks in native MacOSX backend. 429 Also we shouldn't import pyplot, until user does it. Cause it's 430 possible to choose backend before importing pyplot for the first 431 time only. 432 """ 433 def inputhook_mac(app=None): 434 if self.pyplot_imported: 435 pyplot = sys.modules['matplotlib.pyplot'] 436 try: 437 pyplot.pause(0.01) 438 except: 439 pass 440 else: 441 if 'matplotlib.pyplot' in sys.modules: 442 self.pyplot_imported = True 443 444 self.set_inputhook(inputhook_mac) 445 self._current_gui = GUI_OSX 446 447 def disable_mac(self): 448 self.clear_inputhook() 449 450 def current_gui(self): 451 """Return a string indicating the currently active GUI or None.""" 452 return self._current_gui 453 454inputhook_manager = InputHookManager() 455 456enable_wx = inputhook_manager.enable_wx 457disable_wx = inputhook_manager.disable_wx 458enable_qt = inputhook_manager.enable_qt 459enable_qt4 = inputhook_manager.enable_qt4 460disable_qt4 = inputhook_manager.disable_qt4 461enable_qt5 = inputhook_manager.enable_qt5 462disable_qt5 = inputhook_manager.disable_qt5 463enable_gtk = inputhook_manager.enable_gtk 464disable_gtk = inputhook_manager.disable_gtk 465enable_tk = inputhook_manager.enable_tk 466disable_tk = inputhook_manager.disable_tk 467enable_glut = inputhook_manager.enable_glut 468disable_glut = inputhook_manager.disable_glut 469enable_pyglet = inputhook_manager.enable_pyglet 470disable_pyglet = inputhook_manager.disable_pyglet 471enable_gtk3 = inputhook_manager.enable_gtk3 472disable_gtk3 = inputhook_manager.disable_gtk3 473enable_mac = inputhook_manager.enable_mac 474disable_mac = inputhook_manager.disable_mac 475clear_inputhook = inputhook_manager.clear_inputhook 476set_inputhook = inputhook_manager.set_inputhook 477current_gui = inputhook_manager.current_gui 478clear_app_refs = inputhook_manager.clear_app_refs 479 480# We maintain this as stdin_ready so that the individual inputhooks 481# can diverge as little as possible from their IPython sources 482stdin_ready = inputhook_manager.return_control 483set_return_control_callback = inputhook_manager.set_return_control_callback 484get_return_control_callback = inputhook_manager.get_return_control_callback 485get_inputhook = inputhook_manager.get_inputhook 486 487# Convenience function to switch amongst them 488def enable_gui(gui=None, app=None): 489 """Switch amongst GUI input hooks by name. 490 491 This is just a utility wrapper around the methods of the InputHookManager 492 object. 493 494 Parameters 495 ---------- 496 gui : optional, string or None 497 If None (or 'none'), clears input hook, otherwise it must be one 498 of the recognized GUI names (see ``GUI_*`` constants in module). 499 500 app : optional, existing application object. 501 For toolkits that have the concept of a global app, you can supply an 502 existing one. If not given, the toolkit will be probed for one, and if 503 none is found, a new one will be created. Note that GTK does not have 504 this concept, and passing an app if ``gui=="GTK"`` will raise an error. 505 506 Returns 507 ------- 508 The output of the underlying gui switch routine, typically the actual 509 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was 510 one. 511 """ 512 513 if get_return_control_callback() is None: 514 raise ValueError("A return_control_callback must be supplied as a reference before a gui can be enabled") 515 516 guis = {GUI_NONE: clear_inputhook, 517 GUI_OSX: enable_mac, 518 GUI_TK: enable_tk, 519 GUI_GTK: enable_gtk, 520 GUI_WX: enable_wx, 521 GUI_QT: enable_qt, 522 GUI_QT4: enable_qt4, 523 GUI_QT5: enable_qt5, 524 GUI_GLUT: enable_glut, 525 GUI_PYGLET: enable_pyglet, 526 GUI_GTK3: enable_gtk3, 527 } 528 try: 529 gui_hook = guis[gui] 530 except KeyError: 531 if gui is None or gui == '': 532 gui_hook = clear_inputhook 533 else: 534 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys()) 535 raise ValueError(e) 536 return gui_hook(app) 537 538__all__ = [ 539 "GUI_WX", 540 "GUI_QT", 541 "GUI_QT4", 542 "GUI_QT5", 543 "GUI_GTK", 544 "GUI_TK", 545 "GUI_OSX", 546 "GUI_GLUT", 547 "GUI_PYGLET", 548 "GUI_GTK3", 549 "GUI_NONE", 550 551 552 "ignore_CTRL_C", 553 "allow_CTRL_C", 554 555 "InputHookManager", 556 557 "inputhook_manager", 558 559 "enable_wx", 560 "disable_wx", 561 "enable_qt", 562 "enable_qt4", 563 "disable_qt4", 564 "enable_qt5", 565 "disable_qt5", 566 "enable_gtk", 567 "disable_gtk", 568 "enable_tk", 569 "disable_tk", 570 "enable_glut", 571 "disable_glut", 572 "enable_pyglet", 573 "disable_pyglet", 574 "enable_gtk3", 575 "disable_gtk3", 576 "enable_mac", 577 "disable_mac", 578 "clear_inputhook", 579 "set_inputhook", 580 "current_gui", 581 "clear_app_refs", 582 583 "stdin_ready", 584 "set_return_control_callback", 585 "get_return_control_callback", 586 "get_inputhook", 587 588 "enable_gui"] 589