Vendor debugpy for VS Code attach debugging
Bundles debugpy 1.7.0 directly in python/ so "Maya: Attach (debugpy)" works with no per-machine pip install step. Installed via Maya 2022's own pip so it resolved a version actually compatible with Python 3.7 (Maya 2022's interpreter), then verified import + listen() succeeds under all three target Maya Python versions (3.7/3.9/3.10). Also fixes a latent __file__-under-exec() bug in start_debug_server.py (same pitfall as Maya's own plugin loader, never hit until this exercised it) and corrects the README's Maya Python version claim -- 2022 ships Python 3.7, not 3.9 as previously stated, which was never independently verified until now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# Parts of IPython, files from: https://github.com/ipython/ipython/tree/rel-1.0.0/IPython
|
||||
# The files in this package are extracted from IPython to aid the main loop integration
|
||||
# See tests_mainloop for some manually runable tests
|
||||
|
||||
# What we are doing is reusing the "inputhook" functionality (i.e. what in IPython
|
||||
# ends up on PyOS_InputHook) and using it in the pydevconsole context.
|
||||
# Rather that having the callbacks called in PyOS_InputHook, we use a custom XML-RPC
|
||||
# Server (HookableXMLRPCServer) that calls the inputhook when idle
|
||||
@@ -0,0 +1,592 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
Inputhook management for GUI event loop integration.
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2008-2011 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
import select
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Constants
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Constants for identifying the GUI toolkits.
|
||||
GUI_WX = 'wx'
|
||||
GUI_QT = 'qt'
|
||||
GUI_QT4 = 'qt4'
|
||||
GUI_QT5 = 'qt5'
|
||||
GUI_GTK = 'gtk'
|
||||
GUI_TK = 'tk'
|
||||
GUI_OSX = 'osx'
|
||||
GUI_GLUT = 'glut'
|
||||
GUI_PYGLET = 'pyglet'
|
||||
GUI_GTK3 = 'gtk3'
|
||||
GUI_NONE = 'none' # i.e. disable
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Utilities
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def ignore_CTRL_C():
|
||||
"""Ignore CTRL+C (not implemented)."""
|
||||
pass
|
||||
|
||||
|
||||
def allow_CTRL_C():
|
||||
"""Take CTRL+C into account (not implemented)."""
|
||||
pass
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Main InputHookManager class
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class InputHookManager(object):
|
||||
"""Manage PyOS_InputHook for different GUI toolkits.
|
||||
|
||||
This class installs various hooks under ``PyOSInputHook`` to handle
|
||||
GUI event loop integration.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._return_control_callback = None
|
||||
self._apps = {}
|
||||
self._reset()
|
||||
self.pyplot_imported = False
|
||||
|
||||
def _reset(self):
|
||||
self._callback_pyfunctype = None
|
||||
self._callback = None
|
||||
self._current_gui = None
|
||||
|
||||
def set_return_control_callback(self, return_control_callback):
|
||||
self._return_control_callback = return_control_callback
|
||||
|
||||
def get_return_control_callback(self):
|
||||
return self._return_control_callback
|
||||
|
||||
def return_control(self):
|
||||
return self._return_control_callback()
|
||||
|
||||
def get_inputhook(self):
|
||||
return self._callback
|
||||
|
||||
def set_inputhook(self, callback):
|
||||
"""Set inputhook to callback."""
|
||||
# We don't (in the context of PyDev console) actually set PyOS_InputHook, but rather
|
||||
# while waiting for input on xmlrpc we run this code
|
||||
self._callback = callback
|
||||
|
||||
def clear_inputhook(self, app=None):
|
||||
"""Clear input hook.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
app : optional, ignored
|
||||
This parameter is allowed only so that clear_inputhook() can be
|
||||
called with a similar interface as all the ``enable_*`` methods. But
|
||||
the actual value of the parameter is ignored. This uniform interface
|
||||
makes it easier to have user-level entry points in the main IPython
|
||||
app like :meth:`enable_gui`."""
|
||||
self._reset()
|
||||
|
||||
def clear_app_refs(self, gui=None):
|
||||
"""Clear IPython's internal reference to an application instance.
|
||||
|
||||
Whenever we create an app for a user on qt4 or wx, we hold a
|
||||
reference to the app. This is needed because in some cases bad things
|
||||
can happen if a user doesn't hold a reference themselves. This
|
||||
method is provided to clear the references we are holding.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
gui : None or str
|
||||
If None, clear all app references. If ('wx', 'qt4') clear
|
||||
the app for that toolkit. References are not held for gtk or tk
|
||||
as those toolkits don't have the notion of an app.
|
||||
"""
|
||||
if gui is None:
|
||||
self._apps = {}
|
||||
elif gui in self._apps:
|
||||
del self._apps[gui]
|
||||
|
||||
def enable_wx(self, app=None):
|
||||
"""Enable event loop integration with wxPython.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
app : WX Application, optional.
|
||||
Running application to use. If not given, we probe WX for an
|
||||
existing application object, and create a new one if none is found.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This methods sets the ``PyOS_InputHook`` for wxPython, which allows
|
||||
the wxPython to integrate with terminal based applications like
|
||||
IPython.
|
||||
|
||||
If ``app`` is not given we probe for an existing one, and return it if
|
||||
found. If no existing app is found, we create an :class:`wx.App` as
|
||||
follows::
|
||||
|
||||
import wx
|
||||
app = wx.App(redirect=False, clearSigInt=False)
|
||||
"""
|
||||
import wx
|
||||
from distutils.version import LooseVersion as V
|
||||
wx_version = V(wx.__version__).version # @UndefinedVariable
|
||||
|
||||
if wx_version < [2, 8]:
|
||||
raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__) # @UndefinedVariable
|
||||
|
||||
from pydev_ipython.inputhookwx import inputhook_wx
|
||||
self.set_inputhook(inputhook_wx)
|
||||
self._current_gui = GUI_WX
|
||||
|
||||
if app is None:
|
||||
app = wx.GetApp() # @UndefinedVariable
|
||||
if app is None:
|
||||
app = wx.App(redirect=False, clearSigInt=False) # @UndefinedVariable
|
||||
app._in_event_loop = True
|
||||
self._apps[GUI_WX] = app
|
||||
return app
|
||||
|
||||
def disable_wx(self):
|
||||
"""Disable event loop integration with wxPython.
|
||||
|
||||
This merely sets PyOS_InputHook to NULL.
|
||||
"""
|
||||
if GUI_WX in self._apps:
|
||||
self._apps[GUI_WX]._in_event_loop = False
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_qt(self, app=None):
|
||||
from pydev_ipython.qt_for_kernel import QT_API, QT_API_PYQT5
|
||||
if QT_API == QT_API_PYQT5:
|
||||
self.enable_qt5(app)
|
||||
else:
|
||||
self.enable_qt4(app)
|
||||
|
||||
def enable_qt4(self, app=None):
|
||||
"""Enable event loop integration with PyQt4.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
app : Qt Application, optional.
|
||||
Running application to use. If not given, we probe Qt for an
|
||||
existing application object, and create a new one if none is found.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This methods sets the PyOS_InputHook for PyQt4, which allows
|
||||
the PyQt4 to integrate with terminal based applications like
|
||||
IPython.
|
||||
|
||||
If ``app`` is not given we probe for an existing one, and return it if
|
||||
found. If no existing app is found, we create an :class:`QApplication`
|
||||
as follows::
|
||||
|
||||
from PyQt4 import QtCore
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
"""
|
||||
from pydev_ipython.inputhookqt4 import create_inputhook_qt4
|
||||
app, inputhook_qt4 = create_inputhook_qt4(self, app)
|
||||
self.set_inputhook(inputhook_qt4)
|
||||
|
||||
self._current_gui = GUI_QT4
|
||||
app._in_event_loop = True
|
||||
self._apps[GUI_QT4] = app
|
||||
return app
|
||||
|
||||
def disable_qt4(self):
|
||||
"""Disable event loop integration with PyQt4.
|
||||
|
||||
This merely sets PyOS_InputHook to NULL.
|
||||
"""
|
||||
if GUI_QT4 in self._apps:
|
||||
self._apps[GUI_QT4]._in_event_loop = False
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_qt5(self, app=None):
|
||||
from pydev_ipython.inputhookqt5 import create_inputhook_qt5
|
||||
app, inputhook_qt5 = create_inputhook_qt5(self, app)
|
||||
self.set_inputhook(inputhook_qt5)
|
||||
|
||||
self._current_gui = GUI_QT5
|
||||
app._in_event_loop = True
|
||||
self._apps[GUI_QT5] = app
|
||||
return app
|
||||
|
||||
def disable_qt5(self):
|
||||
if GUI_QT5 in self._apps:
|
||||
self._apps[GUI_QT5]._in_event_loop = False
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_gtk(self, app=None):
|
||||
"""Enable event loop integration with PyGTK.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
app : ignored
|
||||
Ignored, it's only a placeholder to keep the call signature of all
|
||||
gui activation methods consistent, which simplifies the logic of
|
||||
supporting magics.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This methods sets the PyOS_InputHook for PyGTK, which allows
|
||||
the PyGTK to integrate with terminal based applications like
|
||||
IPython.
|
||||
"""
|
||||
from pydev_ipython.inputhookgtk import create_inputhook_gtk
|
||||
self.set_inputhook(create_inputhook_gtk(self._stdin_file))
|
||||
self._current_gui = GUI_GTK
|
||||
|
||||
def disable_gtk(self):
|
||||
"""Disable event loop integration with PyGTK.
|
||||
|
||||
This merely sets PyOS_InputHook to NULL.
|
||||
"""
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_tk(self, app=None):
|
||||
"""Enable event loop integration with Tk.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
app : toplevel :class:`Tkinter.Tk` widget, optional.
|
||||
Running toplevel widget to use. If not given, we probe Tk for an
|
||||
existing one, and create a new one if none is found.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If you have already created a :class:`Tkinter.Tk` object, the only
|
||||
thing done by this method is to register with the
|
||||
:class:`InputHookManager`, since creating that object automatically
|
||||
sets ``PyOS_InputHook``.
|
||||
"""
|
||||
self._current_gui = GUI_TK
|
||||
if app is None:
|
||||
try:
|
||||
import Tkinter as _TK
|
||||
except:
|
||||
# Python 3
|
||||
import tkinter as _TK # @UnresolvedImport
|
||||
app = _TK.Tk()
|
||||
app.withdraw()
|
||||
self._apps[GUI_TK] = app
|
||||
|
||||
from pydev_ipython.inputhooktk import create_inputhook_tk
|
||||
self.set_inputhook(create_inputhook_tk(app))
|
||||
return app
|
||||
|
||||
def disable_tk(self):
|
||||
"""Disable event loop integration with Tkinter.
|
||||
|
||||
This merely sets PyOS_InputHook to NULL.
|
||||
"""
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_glut(self, app=None):
|
||||
""" Enable event loop integration with GLUT.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
app : ignored
|
||||
Ignored, it's only a placeholder to keep the call signature of all
|
||||
gui activation methods consistent, which simplifies the logic of
|
||||
supporting magics.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
|
||||
integrate with terminal based applications like IPython. Due to GLUT
|
||||
limitations, it is currently not possible to start the event loop
|
||||
without first creating a window. You should thus not create another
|
||||
window but use instead the created one. See 'gui-glut.py' in the
|
||||
docs/examples/lib directory.
|
||||
|
||||
The default screen mode is set to:
|
||||
glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
|
||||
"""
|
||||
|
||||
import OpenGL.GLUT as glut # @UnresolvedImport
|
||||
from pydev_ipython.inputhookglut import glut_display_mode, \
|
||||
glut_close, glut_display, \
|
||||
glut_idle, inputhook_glut
|
||||
|
||||
if GUI_GLUT not in self._apps:
|
||||
argv = getattr(sys, 'argv', [])
|
||||
glut.glutInit(argv)
|
||||
glut.glutInitDisplayMode(glut_display_mode)
|
||||
# This is specific to freeglut
|
||||
if bool(glut.glutSetOption):
|
||||
glut.glutSetOption(glut.GLUT_ACTION_ON_WINDOW_CLOSE,
|
||||
glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS)
|
||||
glut.glutCreateWindow(argv[0] if len(argv) > 0 else '')
|
||||
glut.glutReshapeWindow(1, 1)
|
||||
glut.glutHideWindow()
|
||||
glut.glutWMCloseFunc(glut_close)
|
||||
glut.glutDisplayFunc(glut_display)
|
||||
glut.glutIdleFunc(glut_idle)
|
||||
else:
|
||||
glut.glutWMCloseFunc(glut_close)
|
||||
glut.glutDisplayFunc(glut_display)
|
||||
glut.glutIdleFunc(glut_idle)
|
||||
self.set_inputhook(inputhook_glut)
|
||||
self._current_gui = GUI_GLUT
|
||||
self._apps[GUI_GLUT] = True
|
||||
|
||||
def disable_glut(self):
|
||||
"""Disable event loop integration with glut.
|
||||
|
||||
This sets PyOS_InputHook to NULL and set the display function to a
|
||||
dummy one and set the timer to a dummy timer that will be triggered
|
||||
very far in the future.
|
||||
"""
|
||||
import OpenGL.GLUT as glut # @UnresolvedImport
|
||||
from glut_support import glutMainLoopEvent # @UnresolvedImport
|
||||
|
||||
glut.glutHideWindow() # This is an event to be processed below
|
||||
glutMainLoopEvent()
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_pyglet(self, app=None):
|
||||
"""Enable event loop integration with pyglet.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
app : ignored
|
||||
Ignored, it's only a placeholder to keep the call signature of all
|
||||
gui activation methods consistent, which simplifies the logic of
|
||||
supporting magics.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This methods sets the ``PyOS_InputHook`` for pyglet, which allows
|
||||
pyglet to integrate with terminal based applications like
|
||||
IPython.
|
||||
|
||||
"""
|
||||
from pydev_ipython.inputhookpyglet import inputhook_pyglet
|
||||
self.set_inputhook(inputhook_pyglet)
|
||||
self._current_gui = GUI_PYGLET
|
||||
return app
|
||||
|
||||
def disable_pyglet(self):
|
||||
"""Disable event loop integration with pyglet.
|
||||
|
||||
This merely sets PyOS_InputHook to NULL.
|
||||
"""
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_gtk3(self, app=None):
|
||||
"""Enable event loop integration with Gtk3 (gir bindings).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
app : ignored
|
||||
Ignored, it's only a placeholder to keep the call signature of all
|
||||
gui activation methods consistent, which simplifies the logic of
|
||||
supporting magics.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This methods sets the PyOS_InputHook for Gtk3, which allows
|
||||
the Gtk3 to integrate with terminal based applications like
|
||||
IPython.
|
||||
"""
|
||||
from pydev_ipython.inputhookgtk3 import create_inputhook_gtk3
|
||||
self.set_inputhook(create_inputhook_gtk3(self._stdin_file))
|
||||
self._current_gui = GUI_GTK
|
||||
|
||||
def disable_gtk3(self):
|
||||
"""Disable event loop integration with PyGTK.
|
||||
|
||||
This merely sets PyOS_InputHook to NULL.
|
||||
"""
|
||||
self.clear_inputhook()
|
||||
|
||||
def enable_mac(self, app=None):
|
||||
""" Enable event loop integration with MacOSX.
|
||||
|
||||
We call function pyplot.pause, which updates and displays active
|
||||
figure during pause. It's not MacOSX-specific, but it enables to
|
||||
avoid inputhooks in native MacOSX backend.
|
||||
Also we shouldn't import pyplot, until user does it. Cause it's
|
||||
possible to choose backend before importing pyplot for the first
|
||||
time only.
|
||||
"""
|
||||
|
||||
def inputhook_mac(app=None):
|
||||
if self.pyplot_imported:
|
||||
pyplot = sys.modules['matplotlib.pyplot']
|
||||
try:
|
||||
pyplot.pause(0.01)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if 'matplotlib.pyplot' in sys.modules:
|
||||
self.pyplot_imported = True
|
||||
|
||||
self.set_inputhook(inputhook_mac)
|
||||
self._current_gui = GUI_OSX
|
||||
|
||||
def disable_mac(self):
|
||||
self.clear_inputhook()
|
||||
|
||||
def current_gui(self):
|
||||
"""Return a string indicating the currently active GUI or None."""
|
||||
return self._current_gui
|
||||
|
||||
|
||||
inputhook_manager = InputHookManager()
|
||||
|
||||
enable_wx = inputhook_manager.enable_wx
|
||||
disable_wx = inputhook_manager.disable_wx
|
||||
enable_qt = inputhook_manager.enable_qt
|
||||
enable_qt4 = inputhook_manager.enable_qt4
|
||||
disable_qt4 = inputhook_manager.disable_qt4
|
||||
enable_qt5 = inputhook_manager.enable_qt5
|
||||
disable_qt5 = inputhook_manager.disable_qt5
|
||||
enable_gtk = inputhook_manager.enable_gtk
|
||||
disable_gtk = inputhook_manager.disable_gtk
|
||||
enable_tk = inputhook_manager.enable_tk
|
||||
disable_tk = inputhook_manager.disable_tk
|
||||
enable_glut = inputhook_manager.enable_glut
|
||||
disable_glut = inputhook_manager.disable_glut
|
||||
enable_pyglet = inputhook_manager.enable_pyglet
|
||||
disable_pyglet = inputhook_manager.disable_pyglet
|
||||
enable_gtk3 = inputhook_manager.enable_gtk3
|
||||
disable_gtk3 = inputhook_manager.disable_gtk3
|
||||
enable_mac = inputhook_manager.enable_mac
|
||||
disable_mac = inputhook_manager.disable_mac
|
||||
clear_inputhook = inputhook_manager.clear_inputhook
|
||||
set_inputhook = inputhook_manager.set_inputhook
|
||||
current_gui = inputhook_manager.current_gui
|
||||
clear_app_refs = inputhook_manager.clear_app_refs
|
||||
|
||||
# We maintain this as stdin_ready so that the individual inputhooks
|
||||
# can diverge as little as possible from their IPython sources
|
||||
stdin_ready = inputhook_manager.return_control
|
||||
set_return_control_callback = inputhook_manager.set_return_control_callback
|
||||
get_return_control_callback = inputhook_manager.get_return_control_callback
|
||||
get_inputhook = inputhook_manager.get_inputhook
|
||||
|
||||
|
||||
# Convenience function to switch amongst them
|
||||
def enable_gui(gui=None, app=None):
|
||||
"""Switch amongst GUI input hooks by name.
|
||||
|
||||
This is just a utility wrapper around the methods of the InputHookManager
|
||||
object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
gui : optional, string or None
|
||||
If None (or 'none'), clears input hook, otherwise it must be one
|
||||
of the recognized GUI names (see ``GUI_*`` constants in module).
|
||||
|
||||
app : optional, existing application object.
|
||||
For toolkits that have the concept of a global app, you can supply an
|
||||
existing one. If not given, the toolkit will be probed for one, and if
|
||||
none is found, a new one will be created. Note that GTK does not have
|
||||
this concept, and passing an app if ``gui=="GTK"`` will raise an error.
|
||||
|
||||
Returns
|
||||
-------
|
||||
The output of the underlying gui switch routine, typically the actual
|
||||
PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
|
||||
one.
|
||||
"""
|
||||
|
||||
if get_return_control_callback() is None:
|
||||
raise ValueError("A return_control_callback must be supplied as a reference before a gui can be enabled")
|
||||
|
||||
guis = {GUI_NONE: clear_inputhook,
|
||||
GUI_OSX: enable_mac,
|
||||
GUI_TK: enable_tk,
|
||||
GUI_GTK: enable_gtk,
|
||||
GUI_WX: enable_wx,
|
||||
GUI_QT: enable_qt,
|
||||
GUI_QT4: enable_qt4,
|
||||
GUI_QT5: enable_qt5,
|
||||
GUI_GLUT: enable_glut,
|
||||
GUI_PYGLET: enable_pyglet,
|
||||
GUI_GTK3: enable_gtk3,
|
||||
}
|
||||
try:
|
||||
gui_hook = guis[gui]
|
||||
except KeyError:
|
||||
if gui is None or gui == '':
|
||||
gui_hook = clear_inputhook
|
||||
else:
|
||||
e = "Invalid GUI request %r, valid ones are:%s" % (gui, list(guis.keys()))
|
||||
raise ValueError(e)
|
||||
return gui_hook(app)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"GUI_WX",
|
||||
"GUI_QT",
|
||||
"GUI_QT4",
|
||||
"GUI_QT5",
|
||||
"GUI_GTK",
|
||||
"GUI_TK",
|
||||
"GUI_OSX",
|
||||
"GUI_GLUT",
|
||||
"GUI_PYGLET",
|
||||
"GUI_GTK3",
|
||||
"GUI_NONE",
|
||||
|
||||
"ignore_CTRL_C",
|
||||
"allow_CTRL_C",
|
||||
|
||||
"InputHookManager",
|
||||
|
||||
"inputhook_manager",
|
||||
|
||||
"enable_wx",
|
||||
"disable_wx",
|
||||
"enable_qt",
|
||||
"enable_qt4",
|
||||
"disable_qt4",
|
||||
"enable_qt5",
|
||||
"disable_qt5",
|
||||
"enable_gtk",
|
||||
"disable_gtk",
|
||||
"enable_tk",
|
||||
"disable_tk",
|
||||
"enable_glut",
|
||||
"disable_glut",
|
||||
"enable_pyglet",
|
||||
"disable_pyglet",
|
||||
"enable_gtk3",
|
||||
"disable_gtk3",
|
||||
"enable_mac",
|
||||
"disable_mac",
|
||||
"clear_inputhook",
|
||||
"set_inputhook",
|
||||
"current_gui",
|
||||
"clear_app_refs",
|
||||
|
||||
"stdin_ready",
|
||||
"set_return_control_callback",
|
||||
"get_return_control_callback",
|
||||
"get_inputhook",
|
||||
|
||||
"enable_gui"]
|
||||
@@ -0,0 +1,154 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
GLUT Inputhook support functions
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2008-2011 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# GLUT is quite an old library and it is difficult to ensure proper
|
||||
# integration within IPython since original GLUT does not allow to handle
|
||||
# events one by one. Instead, it requires for the mainloop to be entered
|
||||
# and never returned (there is not even a function to exit he
|
||||
# mainloop). Fortunately, there are alternatives such as freeglut
|
||||
# (available for linux and windows) and the OSX implementation gives
|
||||
# access to a glutCheckLoop() function that blocks itself until a new
|
||||
# event is received. This means we have to setup the idle callback to
|
||||
# ensure we got at least one event that will unblock the function.
|
||||
#
|
||||
# Furthermore, it is not possible to install these handlers without a window
|
||||
# being first created. We choose to make this window invisible. This means that
|
||||
# display mode options are set at this level and user won't be able to change
|
||||
# them later without modifying the code. This should probably be made available
|
||||
# via IPython options system.
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
import os
|
||||
import sys
|
||||
from _pydev_bundle._pydev_saved_modules import time
|
||||
import signal
|
||||
import OpenGL.GLUT as glut # @UnresolvedImport
|
||||
import OpenGL.platform as platform # @UnresolvedImport
|
||||
from timeit import default_timer as clock
|
||||
from pydev_ipython.inputhook import stdin_ready
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Constants
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Frame per second : 60
|
||||
# Should probably be an IPython option
|
||||
glut_fps = 60
|
||||
|
||||
# Display mode : double buffeed + rgba + depth
|
||||
# Should probably be an IPython option
|
||||
glut_display_mode = (glut.GLUT_DOUBLE |
|
||||
glut.GLUT_RGBA |
|
||||
glut.GLUT_DEPTH)
|
||||
|
||||
glutMainLoopEvent = None
|
||||
if sys.platform == 'darwin':
|
||||
try:
|
||||
glutCheckLoop = platform.createBaseFunction(
|
||||
'glutCheckLoop', dll=platform.GLUT, resultType=None,
|
||||
argTypes=[],
|
||||
doc='glutCheckLoop( ) -> None',
|
||||
argNames=(),
|
||||
)
|
||||
except AttributeError:
|
||||
raise RuntimeError(
|
||||
'''Your glut implementation does not allow interactive sessions'''
|
||||
'''Consider installing freeglut.''')
|
||||
glutMainLoopEvent = glutCheckLoop
|
||||
elif glut.HAVE_FREEGLUT:
|
||||
glutMainLoopEvent = glut.glutMainLoopEvent
|
||||
else:
|
||||
raise RuntimeError(
|
||||
'''Your glut implementation does not allow interactive sessions. '''
|
||||
'''Consider installing freeglut.''')
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Callback functions
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def glut_display():
|
||||
# Dummy display function
|
||||
pass
|
||||
|
||||
|
||||
def glut_idle():
|
||||
# Dummy idle function
|
||||
pass
|
||||
|
||||
|
||||
def glut_close():
|
||||
# Close function only hides the current window
|
||||
glut.glutHideWindow()
|
||||
glutMainLoopEvent()
|
||||
|
||||
|
||||
def glut_int_handler(signum, frame):
|
||||
# Catch sigint and print the defautl message
|
||||
signal.signal(signal.SIGINT, signal.default_int_handler)
|
||||
print('\nKeyboardInterrupt')
|
||||
# Need to reprint the prompt at this stage
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
def inputhook_glut():
|
||||
"""Run the pyglet event loop by processing pending events only.
|
||||
|
||||
This keeps processing pending events until stdin is ready. After
|
||||
processing all pending events, a call to time.sleep is inserted. This is
|
||||
needed, otherwise, CPU usage is at 100%. This sleep time should be tuned
|
||||
though for best performance.
|
||||
"""
|
||||
# We need to protect against a user pressing Control-C when IPython is
|
||||
# idle and this is running. We trap KeyboardInterrupt and pass.
|
||||
|
||||
signal.signal(signal.SIGINT, glut_int_handler)
|
||||
|
||||
try:
|
||||
t = clock()
|
||||
|
||||
# Make sure the default window is set after a window has been closed
|
||||
if glut.glutGetWindow() == 0:
|
||||
glut.glutSetWindow(1)
|
||||
glutMainLoopEvent()
|
||||
return 0
|
||||
|
||||
while not stdin_ready():
|
||||
glutMainLoopEvent()
|
||||
# We need to sleep at this point to keep the idle CPU load
|
||||
# low. However, if sleep to long, GUI response is poor. As
|
||||
# a compromise, we watch how often GUI events are being processed
|
||||
# and switch between a short and long sleep time. Here are some
|
||||
# stats useful in helping to tune this.
|
||||
# time CPU load
|
||||
# 0.001 13%
|
||||
# 0.005 3%
|
||||
# 0.01 1.5%
|
||||
# 0.05 0.5%
|
||||
used_time = clock() - t
|
||||
if used_time > 10.0:
|
||||
# print 'Sleep for 1 s' # dbg
|
||||
time.sleep(1.0)
|
||||
elif used_time > 0.1:
|
||||
# Few GUI events coming in, so we can sleep longer
|
||||
# print 'Sleep for 0.05 s' # dbg
|
||||
time.sleep(0.05)
|
||||
else:
|
||||
# Many GUI events coming in, so sleep only very little
|
||||
time.sleep(0.001)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return 0
|
||||
@@ -0,0 +1,36 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Enable pygtk to be used interacive by setting PyOS_InputHook.
|
||||
|
||||
Authors: Brian Granger
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2008-2011 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import gtk, gobject # @UnresolvedImport
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _main_quit(*args, **kwargs):
|
||||
gtk.main_quit()
|
||||
return False
|
||||
|
||||
def create_inputhook_gtk(stdin_file):
|
||||
def inputhook_gtk():
|
||||
gobject.io_add_watch(stdin_file, gobject.IO_IN, _main_quit)
|
||||
gtk.main()
|
||||
return 0
|
||||
return inputhook_gtk
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Enable Gtk3 to be used interacive by IPython.
|
||||
|
||||
Authors: Thomi Richards
|
||||
"""
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2012, the IPython Development Team.
|
||||
#
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
#
|
||||
# The full license is in the file COPYING.txt, distributed with this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
from gi.repository import Gtk, GLib # @UnresolvedImport
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def _main_quit(*args, **kwargs):
|
||||
Gtk.main_quit()
|
||||
return False
|
||||
|
||||
|
||||
def create_inputhook_gtk3(stdin_file):
|
||||
def inputhook_gtk3():
|
||||
GLib.io_add_watch(stdin_file, GLib.IO_IN, _main_quit)
|
||||
Gtk.main()
|
||||
return 0
|
||||
return inputhook_gtk3
|
||||
@@ -0,0 +1,92 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Enable pyglet to be used interacive by setting PyOS_InputHook.
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
* Nicolas P. Rougier
|
||||
* Fernando Perez
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2008-2011 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
from _pydev_bundle._pydev_saved_modules import time
|
||||
from timeit import default_timer as clock
|
||||
import pyglet # @UnresolvedImport
|
||||
from pydev_ipython.inputhook import stdin_ready
|
||||
|
||||
|
||||
# On linux only, window.flip() has a bug that causes an AttributeError on
|
||||
# window close. For details, see:
|
||||
# http://groups.google.com/group/pyglet-users/browse_thread/thread/47c1aab9aa4a3d23/c22f9e819826799e?#c22f9e819826799e
|
||||
|
||||
if sys.platform.startswith('linux'):
|
||||
def flip(window):
|
||||
try:
|
||||
window.flip()
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
def flip(window):
|
||||
window.flip()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def inputhook_pyglet():
|
||||
"""Run the pyglet event loop by processing pending events only.
|
||||
|
||||
This keeps processing pending events until stdin is ready. After
|
||||
processing all pending events, a call to time.sleep is inserted. This is
|
||||
needed, otherwise, CPU usage is at 100%. This sleep time should be tuned
|
||||
though for best performance.
|
||||
"""
|
||||
# We need to protect against a user pressing Control-C when IPython is
|
||||
# idle and this is running. We trap KeyboardInterrupt and pass.
|
||||
try:
|
||||
t = clock()
|
||||
while not stdin_ready():
|
||||
pyglet.clock.tick()
|
||||
for window in pyglet.app.windows:
|
||||
window.switch_to()
|
||||
window.dispatch_events()
|
||||
window.dispatch_event('on_draw')
|
||||
flip(window)
|
||||
|
||||
# We need to sleep at this point to keep the idle CPU load
|
||||
# low. However, if sleep to long, GUI response is poor. As
|
||||
# a compromise, we watch how often GUI events are being processed
|
||||
# and switch between a short and long sleep time. Here are some
|
||||
# stats useful in helping to tune this.
|
||||
# time CPU load
|
||||
# 0.001 13%
|
||||
# 0.005 3%
|
||||
# 0.01 1.5%
|
||||
# 0.05 0.5%
|
||||
used_time = clock() - t
|
||||
if used_time > 10.0:
|
||||
# print 'Sleep for 1 s' # dbg
|
||||
time.sleep(1.0)
|
||||
elif used_time > 0.1:
|
||||
# Few GUI events coming in, so we can sleep longer
|
||||
# print 'Sleep for 0.05 s' # dbg
|
||||
time.sleep(0.05)
|
||||
else:
|
||||
# Many GUI events coming in, so sleep only very little
|
||||
time.sleep(0.001)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return 0
|
||||
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Qt4's inputhook support function
|
||||
|
||||
Author: Christian Boos
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2011 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import signal
|
||||
|
||||
import threading
|
||||
|
||||
|
||||
from pydev_ipython.qt_for_kernel import QtCore, QtGui
|
||||
from pydev_ipython.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready
|
||||
|
||||
# To minimise future merging complexity, rather than edit the entire code base below
|
||||
# we fake InteractiveShell here
|
||||
class InteractiveShell:
|
||||
_instance = None
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
def set_hook(self, *args, **kwargs):
|
||||
# We don't consider the pre_prompt_hook because we don't have
|
||||
# KeyboardInterrupts to consider since we are running under PyDev
|
||||
pass
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Module Globals
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
got_kbdint = False
|
||||
sigint_timer = None
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def create_inputhook_qt4(mgr, app=None):
|
||||
"""Create an input hook for running the Qt4 application event loop.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mgr : an InputHookManager
|
||||
|
||||
app : Qt Application, optional.
|
||||
Running application to use. If not given, we probe Qt for an
|
||||
existing application object, and create a new one if none is found.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A pair consisting of a Qt Application (either the one given or the
|
||||
one found or created) and a inputhook.
|
||||
|
||||
Notes
|
||||
-----
|
||||
We use a custom input hook instead of PyQt4's default one, as it
|
||||
interacts better with the readline packages (issue #481).
|
||||
|
||||
The inputhook function works in tandem with a 'pre_prompt_hook'
|
||||
which automatically restores the hook as an inputhook in case the
|
||||
latter has been temporarily disabled after having intercepted a
|
||||
KeyboardInterrupt.
|
||||
"""
|
||||
|
||||
if app is None:
|
||||
app = QtCore.QCoreApplication.instance()
|
||||
if app is None:
|
||||
app = QtGui.QApplication([" "])
|
||||
|
||||
# Re-use previously created inputhook if any
|
||||
ip = InteractiveShell.instance()
|
||||
if hasattr(ip, '_inputhook_qt4'):
|
||||
return app, ip._inputhook_qt4
|
||||
|
||||
# Otherwise create the inputhook_qt4/preprompthook_qt4 pair of
|
||||
# hooks (they both share the got_kbdint flag)
|
||||
|
||||
def inputhook_qt4():
|
||||
"""PyOS_InputHook python hook for Qt4.
|
||||
|
||||
Process pending Qt events and if there's no pending keyboard
|
||||
input, spend a short slice of time (50ms) running the Qt event
|
||||
loop.
|
||||
|
||||
As a Python ctypes callback can't raise an exception, we catch
|
||||
the KeyboardInterrupt and temporarily deactivate the hook,
|
||||
which will let a *second* CTRL+C be processed normally and go
|
||||
back to a clean prompt line.
|
||||
"""
|
||||
try:
|
||||
allow_CTRL_C()
|
||||
app = QtCore.QCoreApplication.instance()
|
||||
if not app: # shouldn't happen, but safer if it happens anyway...
|
||||
return 0
|
||||
app.processEvents(QtCore.QEventLoop.AllEvents, 300)
|
||||
if not stdin_ready():
|
||||
# Generally a program would run QCoreApplication::exec()
|
||||
# from main() to enter and process the Qt event loop until
|
||||
# quit() or exit() is called and the program terminates.
|
||||
#
|
||||
# For our input hook integration, we need to repeatedly
|
||||
# enter and process the Qt event loop for only a short
|
||||
# amount of time (say 50ms) to ensure that Python stays
|
||||
# responsive to other user inputs.
|
||||
#
|
||||
# A naive approach would be to repeatedly call
|
||||
# QCoreApplication::exec(), using a timer to quit after a
|
||||
# short amount of time. Unfortunately, QCoreApplication
|
||||
# emits an aboutToQuit signal before stopping, which has
|
||||
# the undesirable effect of closing all modal windows.
|
||||
#
|
||||
# To work around this problem, we instead create a
|
||||
# QEventLoop and call QEventLoop::exec(). Other than
|
||||
# setting some state variables which do not seem to be
|
||||
# used anywhere, the only thing QCoreApplication adds is
|
||||
# the aboutToQuit signal which is precisely what we are
|
||||
# trying to avoid.
|
||||
timer = QtCore.QTimer()
|
||||
event_loop = QtCore.QEventLoop()
|
||||
timer.timeout.connect(event_loop.quit)
|
||||
while not stdin_ready():
|
||||
timer.start(50)
|
||||
event_loop.exec_()
|
||||
timer.stop()
|
||||
except KeyboardInterrupt:
|
||||
global got_kbdint, sigint_timer
|
||||
|
||||
ignore_CTRL_C()
|
||||
got_kbdint = True
|
||||
mgr.clear_inputhook()
|
||||
|
||||
# This generates a second SIGINT so the user doesn't have to
|
||||
# press CTRL+C twice to get a clean prompt.
|
||||
#
|
||||
# Since we can't catch the resulting KeyboardInterrupt here
|
||||
# (because this is a ctypes callback), we use a timer to
|
||||
# generate the SIGINT after we leave this callback.
|
||||
#
|
||||
# Unfortunately this doesn't work on Windows (SIGINT kills
|
||||
# Python and CTRL_C_EVENT doesn't work).
|
||||
if(os.name == 'posix'):
|
||||
pid = os.getpid()
|
||||
if(not sigint_timer):
|
||||
sigint_timer = threading.Timer(.01, os.kill,
|
||||
args=[pid, signal.SIGINT] )
|
||||
sigint_timer.start()
|
||||
else:
|
||||
print("\nKeyboardInterrupt - Ctrl-C again for new prompt")
|
||||
|
||||
|
||||
except: # NO exceptions are allowed to escape from a ctypes callback
|
||||
ignore_CTRL_C()
|
||||
from traceback import print_exc
|
||||
print_exc()
|
||||
print("Got exception from inputhook_qt4, unregistering.")
|
||||
mgr.clear_inputhook()
|
||||
finally:
|
||||
allow_CTRL_C()
|
||||
return 0
|
||||
|
||||
def preprompthook_qt4(ishell):
|
||||
"""'pre_prompt_hook' used to restore the Qt4 input hook
|
||||
|
||||
(in case the latter was temporarily deactivated after a
|
||||
CTRL+C)
|
||||
"""
|
||||
global got_kbdint, sigint_timer
|
||||
|
||||
if(sigint_timer):
|
||||
sigint_timer.cancel()
|
||||
sigint_timer = None
|
||||
|
||||
if got_kbdint:
|
||||
mgr.set_inputhook(inputhook_qt4)
|
||||
got_kbdint = False
|
||||
|
||||
ip._inputhook_qt4 = inputhook_qt4
|
||||
ip.set_hook('pre_prompt_hook', preprompthook_qt4)
|
||||
|
||||
return app, inputhook_qt4
|
||||
@@ -0,0 +1,199 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Qt5's inputhook support function
|
||||
|
||||
Author: Christian Boos
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2011 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import signal
|
||||
|
||||
import threading
|
||||
|
||||
from pydev_ipython.qt_for_kernel import QtCore, QtGui
|
||||
from pydev_ipython.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready
|
||||
|
||||
|
||||
# To minimise future merging complexity, rather than edit the entire code base below
|
||||
# we fake InteractiveShell here
|
||||
class InteractiveShell:
|
||||
_instance = None
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
def set_hook(self, *args, **kwargs):
|
||||
# We don't consider the pre_prompt_hook because we don't have
|
||||
# KeyboardInterrupts to consider since we are running under PyDev
|
||||
pass
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Module Globals
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
got_kbdint = False
|
||||
sigint_timer = None
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def create_inputhook_qt5(mgr, app=None):
|
||||
"""Create an input hook for running the Qt5 application event loop.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mgr : an InputHookManager
|
||||
|
||||
app : Qt Application, optional.
|
||||
Running application to use. If not given, we probe Qt for an
|
||||
existing application object, and create a new one if none is found.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A pair consisting of a Qt Application (either the one given or the
|
||||
one found or created) and a inputhook.
|
||||
|
||||
Notes
|
||||
-----
|
||||
We use a custom input hook instead of PyQt5's default one, as it
|
||||
interacts better with the readline packages (issue #481).
|
||||
|
||||
The inputhook function works in tandem with a 'pre_prompt_hook'
|
||||
which automatically restores the hook as an inputhook in case the
|
||||
latter has been temporarily disabled after having intercepted a
|
||||
KeyboardInterrupt.
|
||||
"""
|
||||
|
||||
if app is None:
|
||||
app = QtCore.QCoreApplication.instance()
|
||||
if app is None:
|
||||
from PyQt5 import QtWidgets
|
||||
app = QtWidgets.QApplication([" "])
|
||||
|
||||
# Re-use previously created inputhook if any
|
||||
ip = InteractiveShell.instance()
|
||||
if hasattr(ip, '_inputhook_qt5'):
|
||||
return app, ip._inputhook_qt5
|
||||
|
||||
# Otherwise create the inputhook_qt5/preprompthook_qt5 pair of
|
||||
# hooks (they both share the got_kbdint flag)
|
||||
|
||||
def inputhook_qt5():
|
||||
"""PyOS_InputHook python hook for Qt5.
|
||||
|
||||
Process pending Qt events and if there's no pending keyboard
|
||||
input, spend a short slice of time (50ms) running the Qt event
|
||||
loop.
|
||||
|
||||
As a Python ctypes callback can't raise an exception, we catch
|
||||
the KeyboardInterrupt and temporarily deactivate the hook,
|
||||
which will let a *second* CTRL+C be processed normally and go
|
||||
back to a clean prompt line.
|
||||
"""
|
||||
try:
|
||||
allow_CTRL_C()
|
||||
app = QtCore.QCoreApplication.instance()
|
||||
if not app: # shouldn't happen, but safer if it happens anyway...
|
||||
return 0
|
||||
app.processEvents(QtCore.QEventLoop.AllEvents, 300)
|
||||
if not stdin_ready():
|
||||
# Generally a program would run QCoreApplication::exec()
|
||||
# from main() to enter and process the Qt event loop until
|
||||
# quit() or exit() is called and the program terminates.
|
||||
#
|
||||
# For our input hook integration, we need to repeatedly
|
||||
# enter and process the Qt event loop for only a short
|
||||
# amount of time (say 50ms) to ensure that Python stays
|
||||
# responsive to other user inputs.
|
||||
#
|
||||
# A naive approach would be to repeatedly call
|
||||
# QCoreApplication::exec(), using a timer to quit after a
|
||||
# short amount of time. Unfortunately, QCoreApplication
|
||||
# emits an aboutToQuit signal before stopping, which has
|
||||
# the undesirable effect of closing all modal windows.
|
||||
#
|
||||
# To work around this problem, we instead create a
|
||||
# QEventLoop and call QEventLoop::exec(). Other than
|
||||
# setting some state variables which do not seem to be
|
||||
# used anywhere, the only thing QCoreApplication adds is
|
||||
# the aboutToQuit signal which is precisely what we are
|
||||
# trying to avoid.
|
||||
timer = QtCore.QTimer()
|
||||
event_loop = QtCore.QEventLoop()
|
||||
timer.timeout.connect(event_loop.quit)
|
||||
while not stdin_ready():
|
||||
timer.start(50)
|
||||
event_loop.exec_()
|
||||
timer.stop()
|
||||
except KeyboardInterrupt:
|
||||
global got_kbdint, sigint_timer
|
||||
|
||||
ignore_CTRL_C()
|
||||
got_kbdint = True
|
||||
mgr.clear_inputhook()
|
||||
|
||||
# This generates a second SIGINT so the user doesn't have to
|
||||
# press CTRL+C twice to get a clean prompt.
|
||||
#
|
||||
# Since we can't catch the resulting KeyboardInterrupt here
|
||||
# (because this is a ctypes callback), we use a timer to
|
||||
# generate the SIGINT after we leave this callback.
|
||||
#
|
||||
# Unfortunately this doesn't work on Windows (SIGINT kills
|
||||
# Python and CTRL_C_EVENT doesn't work).
|
||||
if(os.name == 'posix'):
|
||||
pid = os.getpid()
|
||||
if(not sigint_timer):
|
||||
sigint_timer = threading.Timer(.01, os.kill,
|
||||
args=[pid, signal.SIGINT])
|
||||
sigint_timer.start()
|
||||
else:
|
||||
print("\nKeyboardInterrupt - Ctrl-C again for new prompt")
|
||||
|
||||
except: # NO exceptions are allowed to escape from a ctypes callback
|
||||
ignore_CTRL_C()
|
||||
from traceback import print_exc
|
||||
print_exc()
|
||||
print("Got exception from inputhook_qt5, unregistering.")
|
||||
mgr.clear_inputhook()
|
||||
finally:
|
||||
allow_CTRL_C()
|
||||
return 0
|
||||
|
||||
def preprompthook_qt5(ishell):
|
||||
"""'pre_prompt_hook' used to restore the Qt5 input hook
|
||||
|
||||
(in case the latter was temporarily deactivated after a
|
||||
CTRL+C)
|
||||
"""
|
||||
global got_kbdint, sigint_timer
|
||||
|
||||
if(sigint_timer):
|
||||
sigint_timer.cancel()
|
||||
sigint_timer = None
|
||||
|
||||
if got_kbdint:
|
||||
mgr.set_inputhook(inputhook_qt5)
|
||||
got_kbdint = False
|
||||
|
||||
ip._inputhook_qt5 = inputhook_qt5
|
||||
ip.set_hook('pre_prompt_hook', preprompthook_qt5)
|
||||
|
||||
return app, inputhook_qt5
|
||||
@@ -0,0 +1,23 @@
|
||||
# encoding: utf-8
|
||||
# Unlike what IPython does, we need to have an explicit inputhook because tkinter handles
|
||||
# input hook in the C Source code
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
from pydev_ipython.inputhook import stdin_ready
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
TCL_DONT_WAIT = 1 << 1
|
||||
|
||||
def create_inputhook_tk(app):
|
||||
def inputhook_tk():
|
||||
while app.dooneevent(TCL_DONT_WAIT) == 1:
|
||||
if stdin_ready():
|
||||
break
|
||||
return 0
|
||||
return inputhook_tk
|
||||
@@ -0,0 +1,169 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Enable wxPython to be used interacive by setting PyOS_InputHook.
|
||||
|
||||
Authors: Robin Dunn, Brian Granger, Ondrej Certik
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2008-2011 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
import signal
|
||||
from _pydev_bundle._pydev_saved_modules import time
|
||||
from timeit import default_timer as clock
|
||||
import wx
|
||||
|
||||
from pydev_ipython.inputhook import stdin_ready
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def inputhook_wx1():
|
||||
"""Run the wx event loop by processing pending events only.
|
||||
|
||||
This approach seems to work, but its performance is not great as it
|
||||
relies on having PyOS_InputHook called regularly.
|
||||
"""
|
||||
try:
|
||||
app = wx.GetApp() # @UndefinedVariable
|
||||
if app is not None:
|
||||
assert wx.Thread_IsMain() # @UndefinedVariable
|
||||
|
||||
# Make a temporary event loop and process system events until
|
||||
# there are no more waiting, then allow idle events (which
|
||||
# will also deal with pending or posted wx events.)
|
||||
evtloop = wx.EventLoop() # @UndefinedVariable
|
||||
ea = wx.EventLoopActivator(evtloop) # @UndefinedVariable
|
||||
while evtloop.Pending():
|
||||
evtloop.Dispatch()
|
||||
app.ProcessIdle()
|
||||
del ea
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return 0
|
||||
|
||||
class EventLoopTimer(wx.Timer): # @UndefinedVariable
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
wx.Timer.__init__(self) # @UndefinedVariable
|
||||
|
||||
def Notify(self):
|
||||
self.func()
|
||||
|
||||
class EventLoopRunner(object):
|
||||
|
||||
def Run(self, time):
|
||||
self.evtloop = wx.EventLoop() # @UndefinedVariable
|
||||
self.timer = EventLoopTimer(self.check_stdin)
|
||||
self.timer.Start(time)
|
||||
self.evtloop.Run()
|
||||
|
||||
def check_stdin(self):
|
||||
if stdin_ready():
|
||||
self.timer.Stop()
|
||||
self.evtloop.Exit()
|
||||
|
||||
def inputhook_wx2():
|
||||
"""Run the wx event loop, polling for stdin.
|
||||
|
||||
This version runs the wx eventloop for an undetermined amount of time,
|
||||
during which it periodically checks to see if anything is ready on
|
||||
stdin. If anything is ready on stdin, the event loop exits.
|
||||
|
||||
The argument to elr.Run controls how often the event loop looks at stdin.
|
||||
This determines the responsiveness at the keyboard. A setting of 1000
|
||||
enables a user to type at most 1 char per second. I have found that a
|
||||
setting of 10 gives good keyboard response. We can shorten it further,
|
||||
but eventually performance would suffer from calling select/kbhit too
|
||||
often.
|
||||
"""
|
||||
try:
|
||||
app = wx.GetApp() # @UndefinedVariable
|
||||
if app is not None:
|
||||
assert wx.Thread_IsMain() # @UndefinedVariable
|
||||
elr = EventLoopRunner()
|
||||
# As this time is made shorter, keyboard response improves, but idle
|
||||
# CPU load goes up. 10 ms seems like a good compromise.
|
||||
elr.Run(time=10) # CHANGE time here to control polling interval
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def inputhook_wx3():
|
||||
"""Run the wx event loop by processing pending events only.
|
||||
|
||||
This is like inputhook_wx1, but it keeps processing pending events
|
||||
until stdin is ready. After processing all pending events, a call to
|
||||
time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%.
|
||||
This sleep time should be tuned though for best performance.
|
||||
"""
|
||||
# We need to protect against a user pressing Control-C when IPython is
|
||||
# idle and this is running. We trap KeyboardInterrupt and pass.
|
||||
try:
|
||||
app = wx.GetApp() # @UndefinedVariable
|
||||
if app is not None:
|
||||
if hasattr(wx, 'IsMainThread'):
|
||||
assert wx.IsMainThread() # @UndefinedVariable
|
||||
else:
|
||||
assert wx.Thread_IsMain() # @UndefinedVariable
|
||||
|
||||
# The import of wx on Linux sets the handler for signal.SIGINT
|
||||
# to 0. This is a bug in wx or gtk. We fix by just setting it
|
||||
# back to the Python default.
|
||||
if not callable(signal.getsignal(signal.SIGINT)):
|
||||
signal.signal(signal.SIGINT, signal.default_int_handler)
|
||||
|
||||
evtloop = wx.EventLoop() # @UndefinedVariable
|
||||
ea = wx.EventLoopActivator(evtloop) # @UndefinedVariable
|
||||
t = clock()
|
||||
while not stdin_ready():
|
||||
while evtloop.Pending():
|
||||
t = clock()
|
||||
evtloop.Dispatch()
|
||||
app.ProcessIdle()
|
||||
# We need to sleep at this point to keep the idle CPU load
|
||||
# low. However, if sleep to long, GUI response is poor. As
|
||||
# a compromise, we watch how often GUI events are being processed
|
||||
# and switch between a short and long sleep time. Here are some
|
||||
# stats useful in helping to tune this.
|
||||
# time CPU load
|
||||
# 0.001 13%
|
||||
# 0.005 3%
|
||||
# 0.01 1.5%
|
||||
# 0.05 0.5%
|
||||
used_time = clock() - t
|
||||
if used_time > 10.0:
|
||||
# print 'Sleep for 1 s' # dbg
|
||||
time.sleep(1.0)
|
||||
elif used_time > 0.1:
|
||||
# Few GUI events coming in, so we can sleep longer
|
||||
# print 'Sleep for 0.05 s' # dbg
|
||||
time.sleep(0.05)
|
||||
else:
|
||||
# Many GUI events coming in, so sleep only very little
|
||||
time.sleep(0.001)
|
||||
del ea
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return 0
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
# On OSX, evtloop.Pending() always returns True, regardless of there being
|
||||
# any events pending. As such we can't use implementations 1 or 3 of the
|
||||
# inputhook as those depend on a pending/dispatch loop.
|
||||
inputhook_wx = inputhook_wx2
|
||||
else:
|
||||
# This is our default implementation
|
||||
inputhook_wx = inputhook_wx3
|
||||
@@ -0,0 +1,149 @@
|
||||
|
||||
import sys
|
||||
from _pydev_bundle import pydev_log
|
||||
|
||||
backends = {'tk': 'TkAgg',
|
||||
'gtk': 'GTKAgg',
|
||||
'wx': 'WXAgg',
|
||||
'qt': 'QtAgg', # Auto-choose qt4/5
|
||||
'qt4': 'Qt4Agg',
|
||||
'qt5': 'Qt5Agg',
|
||||
'osx': 'MacOSX'}
|
||||
|
||||
# We also need a reverse backends2guis mapping that will properly choose which
|
||||
# GUI support to activate based on the desired matplotlib backend. For the
|
||||
# most part it's just a reverse of the above dict, but we also need to add a
|
||||
# few others that map to the same GUI manually:
|
||||
backend2gui = dict(zip(backends.values(), backends.keys()))
|
||||
# In the reverse mapping, there are a few extra valid matplotlib backends that
|
||||
# map to the same GUI support
|
||||
backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk'
|
||||
backend2gui['WX'] = 'wx'
|
||||
backend2gui['CocoaAgg'] = 'osx'
|
||||
|
||||
|
||||
def do_enable_gui(guiname):
|
||||
from _pydev_bundle.pydev_versioncheck import versionok_for_gui
|
||||
if versionok_for_gui():
|
||||
try:
|
||||
from pydev_ipython.inputhook import enable_gui
|
||||
enable_gui(guiname)
|
||||
except:
|
||||
sys.stderr.write("Failed to enable GUI event loop integration for '%s'\n" % guiname)
|
||||
pydev_log.exception()
|
||||
elif guiname not in ['none', '', None]:
|
||||
# Only print a warning if the guiname was going to do something
|
||||
sys.stderr.write("Debug console: Python version does not support GUI event loop integration for '%s'\n" % guiname)
|
||||
# Return value does not matter, so return back what was sent
|
||||
return guiname
|
||||
|
||||
|
||||
def find_gui_and_backend():
|
||||
"""Return the gui and mpl backend."""
|
||||
matplotlib = sys.modules['matplotlib']
|
||||
# WARNING: this assumes matplotlib 1.1 or newer!!
|
||||
backend = matplotlib.rcParams['backend']
|
||||
# In this case, we need to find what the appropriate gui selection call
|
||||
# should be for IPython, so we can activate inputhook accordingly
|
||||
gui = backend2gui.get(backend, None)
|
||||
return gui, backend
|
||||
|
||||
|
||||
def is_interactive_backend(backend):
|
||||
""" Check if backend is interactive """
|
||||
matplotlib = sys.modules['matplotlib']
|
||||
from matplotlib.rcsetup import interactive_bk, non_interactive_bk # @UnresolvedImport
|
||||
if backend in interactive_bk:
|
||||
return True
|
||||
elif backend in non_interactive_bk:
|
||||
return False
|
||||
else:
|
||||
return matplotlib.is_interactive()
|
||||
|
||||
|
||||
def patch_use(enable_gui_function):
|
||||
""" Patch matplotlib function 'use' """
|
||||
matplotlib = sys.modules['matplotlib']
|
||||
|
||||
def patched_use(*args, **kwargs):
|
||||
matplotlib.real_use(*args, **kwargs)
|
||||
gui, backend = find_gui_and_backend()
|
||||
enable_gui_function(gui)
|
||||
|
||||
matplotlib.real_use = matplotlib.use
|
||||
matplotlib.use = patched_use
|
||||
|
||||
|
||||
def patch_is_interactive():
|
||||
""" Patch matplotlib function 'use' """
|
||||
matplotlib = sys.modules['matplotlib']
|
||||
|
||||
def patched_is_interactive():
|
||||
return matplotlib.rcParams['interactive']
|
||||
|
||||
matplotlib.real_is_interactive = matplotlib.is_interactive
|
||||
matplotlib.is_interactive = patched_is_interactive
|
||||
|
||||
|
||||
def activate_matplotlib(enable_gui_function):
|
||||
"""Set interactive to True for interactive backends.
|
||||
enable_gui_function - Function which enables gui, should be run in the main thread.
|
||||
"""
|
||||
matplotlib = sys.modules['matplotlib']
|
||||
gui, backend = find_gui_and_backend()
|
||||
is_interactive = is_interactive_backend(backend)
|
||||
if is_interactive:
|
||||
enable_gui_function(gui)
|
||||
if not matplotlib.is_interactive():
|
||||
sys.stdout.write("Backend %s is interactive backend. Turning interactive mode on.\n" % backend)
|
||||
matplotlib.interactive(True)
|
||||
else:
|
||||
if matplotlib.is_interactive():
|
||||
sys.stdout.write("Backend %s is non-interactive backend. Turning interactive mode off.\n" % backend)
|
||||
matplotlib.interactive(False)
|
||||
patch_use(enable_gui_function)
|
||||
patch_is_interactive()
|
||||
|
||||
|
||||
def flag_calls(func):
|
||||
"""Wrap a function to detect and flag when it gets called.
|
||||
|
||||
This is a decorator which takes a function and wraps it in a function with
|
||||
a 'called' attribute. wrapper.called is initialized to False.
|
||||
|
||||
The wrapper.called attribute is set to False right before each call to the
|
||||
wrapped function, so if the call fails it remains False. After the call
|
||||
completes, wrapper.called is set to True and the output is returned.
|
||||
|
||||
Testing for truth in wrapper.called allows you to determine if a call to
|
||||
func() was attempted and succeeded."""
|
||||
|
||||
# don't wrap twice
|
||||
if hasattr(func, 'called'):
|
||||
return func
|
||||
|
||||
def wrapper(*args, **kw):
|
||||
wrapper.called = False
|
||||
out = func(*args, **kw)
|
||||
wrapper.called = True
|
||||
return out
|
||||
|
||||
wrapper.called = False
|
||||
wrapper.__doc__ = func.__doc__
|
||||
return wrapper
|
||||
|
||||
|
||||
def activate_pylab():
|
||||
pylab = sys.modules['pylab']
|
||||
pylab.show._needmain = False
|
||||
# We need to detect at runtime whether show() is called by the user.
|
||||
# For this, we wrap it into a decorator which adds a 'called' flag.
|
||||
pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive)
|
||||
|
||||
|
||||
def activate_pyplot():
|
||||
pyplot = sys.modules['matplotlib.pyplot']
|
||||
pyplot.show._needmain = False
|
||||
# We need to detect at runtime whether show() is called by the user.
|
||||
# For this, we wrap it into a decorator which adds a 'called' flag.
|
||||
pyplot.draw_if_interactive = flag_calls(pyplot.draw_if_interactive)
|
||||
@@ -0,0 +1,23 @@
|
||||
""" A Qt API selector that can be used to switch between PyQt and PySide.
|
||||
|
||||
This uses the ETS 4.0 selection pattern of:
|
||||
PySide first, PyQt with API v2. second.
|
||||
|
||||
Do not use this if you need PyQt with the old QString/QVariant API.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from pydev_ipython.qt_loaders import (load_qt, QT_API_PYSIDE,
|
||||
QT_API_PYQT, QT_API_PYQT5)
|
||||
|
||||
QT_API = os.environ.get('QT_API', None)
|
||||
if QT_API not in [QT_API_PYSIDE, QT_API_PYQT, QT_API_PYQT5, None]:
|
||||
raise RuntimeError("Invalid Qt API %r, valid values are: %r, %r" %
|
||||
(QT_API, QT_API_PYSIDE, QT_API_PYQT, QT_API_PYQT5))
|
||||
if QT_API is None:
|
||||
api_opts = [QT_API_PYSIDE, QT_API_PYQT, QT_API_PYQT5]
|
||||
else:
|
||||
api_opts = [QT_API]
|
||||
|
||||
QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
|
||||
@@ -0,0 +1,119 @@
|
||||
""" Import Qt in a manner suitable for an IPython kernel.
|
||||
|
||||
This is the import used for the `gui=qt` or `matplotlib=qt` initialization.
|
||||
|
||||
Import Priority:
|
||||
|
||||
if Qt4 has been imported anywhere else:
|
||||
use that
|
||||
|
||||
if matplotlib has been imported and doesn't support v2 (<= 1.0.1):
|
||||
use PyQt4 @v1
|
||||
|
||||
Next, ask ETS' QT_API env variable
|
||||
|
||||
if QT_API not set:
|
||||
ask matplotlib via rcParams['backend.qt4']
|
||||
if it said PyQt:
|
||||
use PyQt4 @v1
|
||||
elif it said PySide:
|
||||
use PySide
|
||||
|
||||
else: (matplotlib said nothing)
|
||||
# this is the default path - nobody told us anything
|
||||
try:
|
||||
PyQt @v1
|
||||
except:
|
||||
fallback on PySide
|
||||
else:
|
||||
use PyQt @v2 or PySide, depending on QT_API
|
||||
because ETS doesn't work with PyQt @v1.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pydev_ipython.version import check_version
|
||||
from pydev_ipython.qt_loaders import (load_qt, QT_API_PYSIDE, QT_API_PYSIDE2,
|
||||
QT_API_PYQT, QT_API_PYQT_DEFAULT,
|
||||
loaded_api, QT_API_PYQT5)
|
||||
|
||||
|
||||
# Constraints placed on an imported matplotlib
|
||||
def matplotlib_options(mpl):
|
||||
if mpl is None:
|
||||
return
|
||||
|
||||
# #PyDev-779: In pysrc/pydev_ipython/qt_for_kernel.py, matplotlib_options should be replaced with latest from ipython
|
||||
# (i.e.: properly check backend to decide upon qt4/qt5).
|
||||
|
||||
backend = mpl.rcParams.get('backend', None)
|
||||
if backend == 'Qt4Agg':
|
||||
mpqt = mpl.rcParams.get('backend.qt4', None)
|
||||
if mpqt is None:
|
||||
return None
|
||||
if mpqt.lower() == 'pyside':
|
||||
return [QT_API_PYSIDE]
|
||||
elif mpqt.lower() == 'pyqt4':
|
||||
return [QT_API_PYQT_DEFAULT]
|
||||
elif mpqt.lower() == 'pyqt4v2':
|
||||
return [QT_API_PYQT]
|
||||
raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" %
|
||||
mpqt)
|
||||
|
||||
elif backend == 'Qt5Agg':
|
||||
mpqt = mpl.rcParams.get('backend.qt5', None)
|
||||
if mpqt is None:
|
||||
return None
|
||||
if mpqt.lower() == 'pyqt5':
|
||||
return [QT_API_PYQT5]
|
||||
raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" %
|
||||
mpqt)
|
||||
|
||||
# Fallback without checking backend (previous code)
|
||||
mpqt = mpl.rcParams.get('backend.qt4', None)
|
||||
if mpqt is None:
|
||||
mpqt = mpl.rcParams.get('backend.qt5', None)
|
||||
|
||||
if mpqt is None:
|
||||
return None
|
||||
if mpqt.lower() == 'pyside':
|
||||
return [QT_API_PYSIDE]
|
||||
elif mpqt.lower() == 'pyqt4':
|
||||
return [QT_API_PYQT_DEFAULT]
|
||||
elif mpqt.lower() == 'pyqt5':
|
||||
return [QT_API_PYQT5]
|
||||
raise ImportError("unhandled value for qt backend from matplotlib: %r" %
|
||||
mpqt)
|
||||
|
||||
|
||||
def get_options():
|
||||
"""Return a list of acceptable QT APIs, in decreasing order of
|
||||
preference
|
||||
"""
|
||||
# already imported Qt somewhere. Use that
|
||||
loaded = loaded_api()
|
||||
if loaded is not None:
|
||||
return [loaded]
|
||||
|
||||
mpl = sys.modules.get('matplotlib', None)
|
||||
|
||||
if mpl is not None and not check_version(mpl.__version__, '1.0.2'):
|
||||
# 1.0.1 only supports PyQt4 v1
|
||||
return [QT_API_PYQT_DEFAULT]
|
||||
|
||||
if os.environ.get('QT_API', None) is None:
|
||||
# no ETS variable. Ask mpl, then use either
|
||||
return matplotlib_options(mpl) or [QT_API_PYQT_DEFAULT, QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYQT5]
|
||||
|
||||
# ETS variable present. Will fallback to external.qt
|
||||
return None
|
||||
|
||||
|
||||
api_opts = get_options()
|
||||
if api_opts is not None:
|
||||
QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
|
||||
|
||||
else: # use ETS variable
|
||||
from pydev_ipython.qt import QtCore, QtGui, QtSvg, QT_API
|
||||
@@ -0,0 +1,301 @@
|
||||
"""
|
||||
This module contains factory functions that attempt
|
||||
to return Qt submodules from the various python Qt bindings.
|
||||
|
||||
It also protects against double-importing Qt with different
|
||||
bindings, which is unstable and likely to crash
|
||||
|
||||
This is used primarily by qt and qt_for_kernel, and shouldn't
|
||||
be accessed directly from the outside
|
||||
"""
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from pydev_ipython.version import check_version
|
||||
|
||||
# Available APIs.
|
||||
QT_API_PYQT = 'pyqt'
|
||||
QT_API_PYQTv1 = 'pyqtv1'
|
||||
QT_API_PYQT_DEFAULT = 'pyqtdefault' # don't set SIP explicitly
|
||||
QT_API_PYSIDE = 'pyside'
|
||||
QT_API_PYSIDE2 = 'pyside2'
|
||||
QT_API_PYQT5 = 'pyqt5'
|
||||
|
||||
|
||||
class ImportDenier(object):
|
||||
"""Import Hook that will guard against bad Qt imports
|
||||
once IPython commits to a specific binding
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.__forbidden = set()
|
||||
|
||||
def forbid(self, module_name):
|
||||
sys.modules.pop(module_name, None)
|
||||
self.__forbidden.add(module_name)
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if path:
|
||||
return
|
||||
if fullname in self.__forbidden:
|
||||
return self
|
||||
|
||||
def load_module(self, fullname):
|
||||
raise ImportError("""
|
||||
Importing %s disabled by IPython, which has
|
||||
already imported an Incompatible QT Binding: %s
|
||||
""" % (fullname, loaded_api()))
|
||||
|
||||
|
||||
ID = ImportDenier()
|
||||
sys.meta_path.append(ID)
|
||||
|
||||
|
||||
def commit_api(api):
|
||||
"""Commit to a particular API, and trigger ImportErrors on subsequent
|
||||
dangerous imports"""
|
||||
|
||||
if api == QT_API_PYSIDE:
|
||||
ID.forbid('PyQt4')
|
||||
ID.forbid('PyQt5')
|
||||
else:
|
||||
ID.forbid('PySide')
|
||||
ID.forbid('PySide2')
|
||||
|
||||
|
||||
def loaded_api():
|
||||
"""Return which API is loaded, if any
|
||||
|
||||
If this returns anything besides None,
|
||||
importing any other Qt binding is unsafe.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None, 'pyside', 'pyside2', 'pyqt', or 'pyqtv1'
|
||||
"""
|
||||
if 'PyQt4.QtCore' in sys.modules:
|
||||
if qtapi_version() == 2:
|
||||
return QT_API_PYQT
|
||||
else:
|
||||
return QT_API_PYQTv1
|
||||
elif 'PySide.QtCore' in sys.modules:
|
||||
return QT_API_PYSIDE
|
||||
elif 'PySide2.QtCore' in sys.modules:
|
||||
return QT_API_PYSIDE2
|
||||
elif 'PyQt5.QtCore' in sys.modules:
|
||||
return QT_API_PYQT5
|
||||
return None
|
||||
|
||||
|
||||
def has_binding(api):
|
||||
"""Safely check for PyQt4 or PySide, without importing
|
||||
submodules
|
||||
|
||||
Parameters
|
||||
----------
|
||||
api : str [ 'pyqtv1' | 'pyqt' | 'pyside' | 'pyqtdefault']
|
||||
Which module to check for
|
||||
|
||||
Returns
|
||||
-------
|
||||
True if the relevant module appears to be importable
|
||||
"""
|
||||
# we can't import an incomplete pyside and pyqt4
|
||||
# this will cause a crash in sip (#1431)
|
||||
# check for complete presence before importing
|
||||
module_name = {QT_API_PYSIDE: 'PySide',
|
||||
QT_API_PYSIDE2: 'PySide2',
|
||||
QT_API_PYQT: 'PyQt4',
|
||||
QT_API_PYQTv1: 'PyQt4',
|
||||
QT_API_PYQT_DEFAULT: 'PyQt4',
|
||||
QT_API_PYQT5: 'PyQt5',
|
||||
}
|
||||
module_name = module_name[api]
|
||||
|
||||
import imp
|
||||
try:
|
||||
# importing top level PyQt4/PySide module is ok...
|
||||
mod = __import__(module_name)
|
||||
# ...importing submodules is not
|
||||
imp.find_module('QtCore', mod.__path__)
|
||||
imp.find_module('QtGui', mod.__path__)
|
||||
imp.find_module('QtSvg', mod.__path__)
|
||||
|
||||
# we can also safely check PySide version
|
||||
if api == QT_API_PYSIDE:
|
||||
return check_version(mod.__version__, '1.0.3')
|
||||
else:
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
def qtapi_version():
|
||||
"""Return which QString API has been set, if any
|
||||
|
||||
Returns
|
||||
-------
|
||||
The QString API version (1 or 2), or None if not set
|
||||
"""
|
||||
try:
|
||||
import sip
|
||||
except ImportError:
|
||||
return
|
||||
try:
|
||||
return sip.getapi('QString')
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
|
||||
def can_import(api):
|
||||
"""Safely query whether an API is importable, without importing it"""
|
||||
if not has_binding(api):
|
||||
return False
|
||||
|
||||
current = loaded_api()
|
||||
if api == QT_API_PYQT_DEFAULT:
|
||||
return current in [QT_API_PYQT, QT_API_PYQTv1, QT_API_PYQT5, None]
|
||||
else:
|
||||
return current in [api, None]
|
||||
|
||||
|
||||
def import_pyqt4(version=2):
|
||||
"""
|
||||
Import PyQt4
|
||||
|
||||
Parameters
|
||||
----------
|
||||
version : 1, 2, or None
|
||||
Which QString/QVariant API to use. Set to None to use the system
|
||||
default
|
||||
|
||||
ImportErrors raised within this function are non-recoverable
|
||||
"""
|
||||
# The new-style string API (version=2) automatically
|
||||
# converts QStrings to Unicode Python strings. Also, automatically unpacks
|
||||
# QVariants to their underlying objects.
|
||||
import sip
|
||||
|
||||
if version is not None:
|
||||
sip.setapi('QString', version)
|
||||
sip.setapi('QVariant', version)
|
||||
|
||||
from PyQt4 import QtGui, QtCore, QtSvg
|
||||
|
||||
if not check_version(QtCore.PYQT_VERSION_STR, '4.7'):
|
||||
raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
|
||||
QtCore.PYQT_VERSION_STR)
|
||||
|
||||
# Alias PyQt-specific functions for PySide compatibility.
|
||||
QtCore.Signal = QtCore.pyqtSignal
|
||||
QtCore.Slot = QtCore.pyqtSlot
|
||||
|
||||
# query for the API version (in case version == None)
|
||||
version = sip.getapi('QString')
|
||||
api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
|
||||
return QtCore, QtGui, QtSvg, api
|
||||
|
||||
|
||||
def import_pyqt5():
|
||||
"""
|
||||
Import PyQt5
|
||||
|
||||
ImportErrors raised within this function are non-recoverable
|
||||
"""
|
||||
from PyQt5 import QtGui, QtCore, QtSvg
|
||||
|
||||
# Alias PyQt-specific functions for PySide compatibility.
|
||||
QtCore.Signal = QtCore.pyqtSignal
|
||||
QtCore.Slot = QtCore.pyqtSlot
|
||||
|
||||
return QtCore, QtGui, QtSvg, QT_API_PYQT5
|
||||
|
||||
|
||||
def import_pyside():
|
||||
"""
|
||||
Import PySide
|
||||
|
||||
ImportErrors raised within this function are non-recoverable
|
||||
"""
|
||||
from PySide import QtGui, QtCore, QtSvg # @UnresolvedImport
|
||||
return QtCore, QtGui, QtSvg, QT_API_PYSIDE
|
||||
|
||||
|
||||
def import_pyside2():
|
||||
"""
|
||||
Import PySide2
|
||||
|
||||
ImportErrors raised within this function are non-recoverable
|
||||
"""
|
||||
from PySide2 import QtGui, QtCore, QtSvg # @UnresolvedImport
|
||||
return QtCore, QtGui, QtSvg, QT_API_PYSIDE
|
||||
|
||||
|
||||
def load_qt(api_options):
|
||||
"""
|
||||
Attempt to import Qt, given a preference list
|
||||
of permissible bindings
|
||||
|
||||
It is safe to call this function multiple times.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
api_options: List of strings
|
||||
The order of APIs to try. Valid items are 'pyside',
|
||||
'pyqt', and 'pyqtv1'
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
A tuple of QtCore, QtGui, QtSvg, QT_API
|
||||
The first three are the Qt modules. The last is the
|
||||
string indicating which module was loaded.
|
||||
|
||||
Raises
|
||||
------
|
||||
ImportError, if it isn't possible to import any requested
|
||||
bindings (either becaues they aren't installed, or because
|
||||
an incompatible library has already been installed)
|
||||
"""
|
||||
loaders = {QT_API_PYSIDE: import_pyside,
|
||||
QT_API_PYSIDE2: import_pyside2,
|
||||
QT_API_PYQT: import_pyqt4,
|
||||
QT_API_PYQTv1: partial(import_pyqt4, version=1),
|
||||
QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None),
|
||||
QT_API_PYQT5: import_pyqt5,
|
||||
}
|
||||
|
||||
for api in api_options:
|
||||
|
||||
if api not in loaders:
|
||||
raise RuntimeError(
|
||||
"Invalid Qt API %r, valid values are: %r, %r, %r, %r, %r, %r" %
|
||||
(api, QT_API_PYSIDE, QT_API_PYSIDE, QT_API_PYQT,
|
||||
QT_API_PYQTv1, QT_API_PYQT_DEFAULT, QT_API_PYQT5))
|
||||
|
||||
if not can_import(api):
|
||||
continue
|
||||
|
||||
# cannot safely recover from an ImportError during this
|
||||
result = loaders[api]()
|
||||
api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
|
||||
commit_api(api)
|
||||
return result
|
||||
else:
|
||||
raise ImportError("""
|
||||
Could not load requested Qt binding. Please ensure that
|
||||
PyQt4 >= 4.7 or PySide >= 1.0.3 is available,
|
||||
and only one is imported per session.
|
||||
|
||||
Currently-imported Qt library: %r
|
||||
PyQt4 installed: %s
|
||||
PyQt5 installed: %s
|
||||
PySide >= 1.0.3 installed: %s
|
||||
PySide2 installed: %s
|
||||
Tried to load: %r
|
||||
""" % (loaded_api(),
|
||||
has_binding(QT_API_PYQT),
|
||||
has_binding(QT_API_PYQT5),
|
||||
has_binding(QT_API_PYSIDE),
|
||||
has_binding(QT_API_PYSIDE2),
|
||||
api_options))
|
||||
@@ -0,0 +1,36 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Utilities for version comparison
|
||||
|
||||
It is a bit ridiculous that we need these.
|
||||
"""
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (C) 2013 The IPython Development Team
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Imports
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Code
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def check_version(v, check):
|
||||
"""check version string v >= check
|
||||
|
||||
If dev/prerelease tags result in TypeError for string-number comparison,
|
||||
it is assumed that the dependency is satisfied.
|
||||
Users on dev branches are responsible for keeping their own packages up to date.
|
||||
"""
|
||||
try:
|
||||
return LooseVersion(v) >= LooseVersion(check)
|
||||
except TypeError:
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user