mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
254 lines
8.8 KiB
Python
254 lines
8.8 KiB
Python
from __future__ import nested_scopes
|
|
|
|
from _pydev_imps._pydev_saved_modules import threading
|
|
from _pydevd_bundle.pydevd_constants import IS_PY38
|
|
import os
|
|
|
|
def set_trace_in_qt():
|
|
from _pydevd_bundle.pydevd_comm import get_global_debugger
|
|
debugger = get_global_debugger()
|
|
if debugger is not None:
|
|
threading.current_thread() # Create the dummy thread for qt.
|
|
debugger.enable_tracing()
|
|
|
|
|
|
_patched_qt = False
|
|
def patch_qt(qt_support_mode):
|
|
'''
|
|
This method patches qt (PySide, PyQt4, PyQt5) so that we have hooks to set the tracing for QThread.
|
|
'''
|
|
if not qt_support_mode:
|
|
return
|
|
|
|
if qt_support_mode is True or qt_support_mode == 'True':
|
|
# do not break backward compatibility
|
|
qt_support_mode = 'auto'
|
|
|
|
if qt_support_mode == 'auto':
|
|
qt_support_mode = os.getenv('PYDEVD_PYQT_MODE', 'auto')
|
|
|
|
# Avoid patching more than once
|
|
global _patched_qt
|
|
if _patched_qt:
|
|
return
|
|
|
|
_patched_qt = True
|
|
|
|
if qt_support_mode == 'auto':
|
|
|
|
patch_qt_on_import = None
|
|
try:
|
|
if IS_PY38:
|
|
raise ImportError
|
|
import PySide6
|
|
qt_support_mode = 'pyside6'
|
|
except:
|
|
try:
|
|
# PY-50959
|
|
# Problem:
|
|
# 1. We have Python 3.8;
|
|
# 2. PyQt compatible = Auto or PySide2;
|
|
# 3. We try to import numpy, we get "AttributeError: module 'numpy.core' has no attribute 'numerictypes'"
|
|
#
|
|
# Solution:
|
|
# We decided to turn off patching for PySide2 if we have Python 3.8
|
|
# Here we skip 'import PySide2' and keep trying to import another qt libraries
|
|
if IS_PY38:
|
|
raise ImportError
|
|
import PySide2 # @UnresolvedImport @UnusedImport
|
|
qt_support_mode = 'pyside2'
|
|
except:
|
|
try:
|
|
import Pyside # @UnresolvedImport @UnusedImport
|
|
qt_support_mode = 'pyside'
|
|
except:
|
|
try:
|
|
import PyQt6 # @UnresolvedImport @UnusedImport
|
|
qt_support_mode = 'pyqt6'
|
|
except:
|
|
try:
|
|
import PyQt5 # @UnresolvedImport @UnusedImport
|
|
qt_support_mode = 'pyqt5'
|
|
except:
|
|
try:
|
|
import PyQt4 # @UnresolvedImport @UnusedImport
|
|
qt_support_mode = 'pyqt4'
|
|
except:
|
|
return
|
|
|
|
if qt_support_mode == 'pyside6':
|
|
if IS_PY38:
|
|
return
|
|
try:
|
|
import PySide6.QtCore # @UnresolvedImport
|
|
_internal_patch_qt(PySide6.QtCore, qt_support_mode)
|
|
except:
|
|
return
|
|
elif qt_support_mode == 'pyside2':
|
|
# PY-50959
|
|
# We can get here only if PyQt compatible = PySide2, in this case we should return
|
|
# See comment above about PY-50959
|
|
if IS_PY38:
|
|
return
|
|
try:
|
|
import PySide2.QtCore # @UnresolvedImport
|
|
_internal_patch_qt(PySide2.QtCore, qt_support_mode)
|
|
except:
|
|
return
|
|
|
|
elif qt_support_mode == 'pyside':
|
|
try:
|
|
import PySide.QtCore # @UnresolvedImport
|
|
_internal_patch_qt(PySide.QtCore, qt_support_mode)
|
|
except:
|
|
return
|
|
|
|
elif qt_support_mode == 'pyqt6':
|
|
try:
|
|
import PyQt6.QtCore # @UnresolvedImport
|
|
_internal_patch_qt(PyQt6.QtCore)
|
|
except:
|
|
return
|
|
|
|
elif qt_support_mode == 'pyqt5':
|
|
try:
|
|
import PyQt5.QtCore # @UnresolvedImport
|
|
_internal_patch_qt(PyQt5.QtCore)
|
|
except:
|
|
return
|
|
|
|
elif qt_support_mode == 'pyqt4':
|
|
# Ok, we have an issue here:
|
|
# PyDev-452: Selecting PyQT API version using sip.setapi fails in debug mode
|
|
# http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
|
|
# Mostly, if the user uses a different API version (i.e.: v2 instead of v1),
|
|
# that has to be done before importing PyQt4 modules (PySide/PyQt5 don't have this issue
|
|
# as they only implements v2).
|
|
patch_qt_on_import = 'PyQt4'
|
|
def get_qt_core_module():
|
|
import PyQt4.QtCore # @UnresolvedImport
|
|
return PyQt4.QtCore
|
|
_patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module)
|
|
|
|
else:
|
|
raise ValueError('Unexpected qt support mode: %s' % (qt_support_mode,))
|
|
|
|
|
|
def _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module):
|
|
# I don't like this approach very much as we have to patch __import__, but I like even less
|
|
# asking the user to configure something in the client side...
|
|
# So, our approach is to patch PyQt4 right before the user tries to import it (at which
|
|
# point he should've set the sip api version properly already anyways).
|
|
|
|
dotted = patch_qt_on_import + '.'
|
|
original_import = __import__
|
|
|
|
from _pydev_imps._pydev_sys_patch import patch_sys_module, patch_reload, cancel_patches_in_sys_module
|
|
|
|
patch_sys_module()
|
|
patch_reload()
|
|
|
|
def patched_import(name, *args, **kwargs):
|
|
if patch_qt_on_import == name or name.startswith(dotted):
|
|
builtins.__import__ = original_import
|
|
cancel_patches_in_sys_module()
|
|
_internal_patch_qt(get_qt_core_module()) # Patch it only when the user would import the qt module
|
|
return original_import(name, *args, **kwargs)
|
|
|
|
import sys
|
|
if sys.version_info[0] >= 3:
|
|
import builtins # Py3
|
|
else:
|
|
import __builtin__ as builtins
|
|
|
|
builtins.__import__ = patched_import
|
|
|
|
|
|
def _internal_patch_qt(QtCore, qt_support_mode='auto'):
|
|
_original_thread_init = QtCore.QThread.__init__
|
|
_original_runnable_init = QtCore.QRunnable.__init__
|
|
_original_QThread = QtCore.QThread
|
|
|
|
class FuncWrapper:
|
|
def __init__(self, original):
|
|
self._original = original
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
set_trace_in_qt()
|
|
return self._original(*args, **kwargs)
|
|
|
|
class StartedSignalWrapper(QtCore.QObject): # Wrapper for the QThread.started signal
|
|
|
|
try:
|
|
_signal = QtCore.Signal() # @UndefinedVariable
|
|
except:
|
|
_signal = QtCore.pyqtSignal() # @UndefinedVariable
|
|
|
|
def __init__(self, thread, original_started):
|
|
QtCore.QObject.__init__(self)
|
|
self.thread = thread
|
|
self.original_started = original_started
|
|
if qt_support_mode in ('pyside', 'pyside2', 'pyside6'):
|
|
self._signal = original_started
|
|
else:
|
|
self._signal.connect(self._on_call)
|
|
self.original_started.connect(self._signal)
|
|
|
|
def connect(self, func, *args, **kwargs):
|
|
_is_signal_instance = False
|
|
if hasattr(QtCore, 'SignalInstance'):
|
|
_is_signal_instance = isinstance(func, QtCore.SignalInstance)
|
|
if qt_support_mode in ('pyside', 'pyside2', 'pyside6') and not _is_signal_instance:
|
|
return self._signal.connect(FuncWrapper(func), *args, **kwargs)
|
|
else:
|
|
return self._signal.connect(func, *args, **kwargs)
|
|
|
|
def disconnect(self, *args, **kwargs):
|
|
return self._signal.disconnect(*args, **kwargs)
|
|
|
|
def emit(self, *args, **kwargs):
|
|
return self._signal.emit(*args, **kwargs)
|
|
|
|
def _on_call(self, *args, **kwargs):
|
|
set_trace_in_qt()
|
|
|
|
class ThreadWrapper(QtCore.QThread): # Wrapper for QThread
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
_original_thread_init(self, *args, **kwargs)
|
|
|
|
# In PyQt5 the program hangs when we try to call original run method of QThread class.
|
|
# So we need to distinguish instances of QThread class and instances of QThread inheritors.
|
|
if self.__class__.run == _original_QThread.run:
|
|
self.run = self._exec_run
|
|
else:
|
|
self._original_run = self.run
|
|
self.run = self._new_run
|
|
self._original_started = self.started
|
|
self.started = StartedSignalWrapper(self, self.started)
|
|
|
|
def _exec_run(self):
|
|
set_trace_in_qt()
|
|
self.exec_()
|
|
return None
|
|
|
|
def _new_run(self):
|
|
set_trace_in_qt()
|
|
return self._original_run()
|
|
|
|
class RunnableWrapper(QtCore.QRunnable): # Wrapper for QRunnable
|
|
|
|
def __init__(self, *args):
|
|
_original_runnable_init(self, *args)
|
|
|
|
self._original_run = self.run
|
|
self.run = self._new_run
|
|
|
|
|
|
def _new_run(self):
|
|
set_trace_in_qt()
|
|
return self._original_run()
|
|
|
|
QtCore.QThread = ThreadWrapper
|
|
QtCore.QRunnable = RunnableWrapper
|