1from __future__ import nested_scopes
2
3from _pydev_imps._pydev_saved_modules import threading
4import os
5
6def set_trace_in_qt():
7    from _pydevd_bundle.pydevd_comm import get_global_debugger
8    debugger = get_global_debugger()
9    if debugger is not None:
10        threading.current_thread()  # Create the dummy thread for qt.
11        debugger.enable_tracing()
12
13
14_patched_qt = False
15def patch_qt(qt_support_mode):
16    '''
17    This method patches qt (PySide, PyQt4, PyQt5) so that we have hooks to set the tracing for QThread.
18    '''
19    if not qt_support_mode:
20        return
21
22    if qt_support_mode is True or qt_support_mode == 'True':
23        # do not break backward compatibility
24        qt_support_mode = 'auto'
25
26    if qt_support_mode == 'auto':
27        qt_support_mode = os.getenv('PYDEVD_PYQT_MODE', 'auto')
28
29    # Avoid patching more than once
30    global _patched_qt
31    if _patched_qt:
32        return
33
34    _patched_qt = True
35
36    if qt_support_mode == 'auto':
37
38        patch_qt_on_import = None
39        try:
40            import PySide2  # @UnresolvedImport @UnusedImport
41            qt_support_mode = 'pyside2'
42        except:
43            try:
44                import Pyside  # @UnresolvedImport @UnusedImport
45                qt_support_mode = 'pyside'
46            except:
47                try:
48                    import PyQt5  # @UnresolvedImport @UnusedImport
49                    qt_support_mode = 'pyqt5'
50                except:
51                    try:
52                        import PyQt4  # @UnresolvedImport @UnusedImport
53                        qt_support_mode = 'pyqt4'
54                    except:
55                        return
56
57    if qt_support_mode == 'pyside2':
58        try:
59            import PySide2.QtCore  # @UnresolvedImport
60            _internal_patch_qt(PySide2.QtCore, qt_support_mode)
61        except:
62            return
63
64    elif qt_support_mode == 'pyside':
65        try:
66            import PySide.QtCore  # @UnresolvedImport
67            _internal_patch_qt(PySide.QtCore, qt_support_mode)
68        except:
69            return
70
71    elif qt_support_mode == 'pyqt5':
72        try:
73            import PyQt5.QtCore  # @UnresolvedImport
74            _internal_patch_qt(PyQt5.QtCore)
75        except:
76            return
77
78    elif qt_support_mode == 'pyqt4':
79        # Ok, we have an issue here:
80        # PyDev-452: Selecting PyQT API version using sip.setapi fails in debug mode
81        # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
82        # Mostly, if the user uses a different API version (i.e.: v2 instead of v1),
83        # that has to be done before importing PyQt4 modules (PySide/PyQt5 don't have this issue
84        # as they only implements v2).
85        patch_qt_on_import = 'PyQt4'
86        def get_qt_core_module():
87            import PyQt4.QtCore  # @UnresolvedImport
88            return PyQt4.QtCore
89        _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module)
90
91    else:
92        raise ValueError('Unexpected qt support mode: %s' % (qt_support_mode,))
93
94
95def _patch_import_to_patch_pyqt_on_import(patch_qt_on_import, get_qt_core_module):
96    # I don't like this approach very much as we have to patch __import__, but I like even less
97    # asking the user to configure something in the client side...
98    # So, our approach is to patch PyQt4 right before the user tries to import it (at which
99    # point he should've set the sip api version properly already anyways).
100
101    dotted = patch_qt_on_import + '.'
102    original_import = __import__
103
104    from _pydev_imps._pydev_sys_patch import patch_sys_module, patch_reload, cancel_patches_in_sys_module
105
106    patch_sys_module()
107    patch_reload()
108
109    def patched_import(name, *args, **kwargs):
110        if patch_qt_on_import == name or name.startswith(dotted):
111            builtins.__import__ = original_import
112            cancel_patches_in_sys_module()
113            _internal_patch_qt(get_qt_core_module()) # Patch it only when the user would import the qt module
114        return original_import(name, *args, **kwargs)
115
116    import sys
117    if sys.version_info[0] >= 3:
118        import builtins # Py3
119    else:
120        import __builtin__ as builtins
121
122    builtins.__import__ = patched_import
123
124
125def _internal_patch_qt(QtCore, qt_support_mode='auto'):
126    _original_thread_init = QtCore.QThread.__init__
127    _original_runnable_init = QtCore.QRunnable.__init__
128    _original_QThread = QtCore.QThread
129
130    class FuncWrapper:
131        def __init__(self, original):
132            self._original = original
133
134        def __call__(self, *args, **kwargs):
135            set_trace_in_qt()
136            return self._original(*args, **kwargs)
137
138    class StartedSignalWrapper(QtCore.QObject):  # Wrapper for the QThread.started signal
139
140        try:
141            _signal = QtCore.Signal()  # @UndefinedVariable
142        except:
143            _signal = QtCore.pyqtSignal()  # @UndefinedVariable
144
145        def __init__(self, thread, original_started):
146            QtCore.QObject.__init__(self)
147            self.thread = thread
148            self.original_started = original_started
149            if qt_support_mode in ('pyside', 'pyside2'):
150                self._signal = original_started
151            else:
152                self._signal.connect(self._on_call)
153                self.original_started.connect(self._signal)
154
155        def connect(self, func, *args, **kwargs):
156            if qt_support_mode in ('pyside', 'pyside2'):
157                return self._signal.connect(FuncWrapper(func), *args, **kwargs)
158            else:
159                return self._signal.connect(func, *args, **kwargs)
160
161        def disconnect(self, *args, **kwargs):
162            return self._signal.disconnect(*args, **kwargs)
163
164        def emit(self, *args, **kwargs):
165            return self._signal.emit(*args, **kwargs)
166
167        def _on_call(self, *args, **kwargs):
168            set_trace_in_qt()
169
170    class ThreadWrapper(QtCore.QThread):  # Wrapper for QThread
171
172        def __init__(self, *args, **kwargs):
173            _original_thread_init(self, *args, **kwargs)
174
175            # In PyQt5 the program hangs when we try to call original run method of QThread class.
176            # So we need to distinguish instances of QThread class and instances of QThread inheritors.
177            if self.__class__.run == _original_QThread.run:
178                self.run = self._exec_run
179            else:
180                self._original_run = self.run
181                self.run = self._new_run
182            self._original_started = self.started
183            self.started = StartedSignalWrapper(self, self.started)
184
185        def _exec_run(self):
186            set_trace_in_qt()
187            self.exec_()
188            return None
189
190        def _new_run(self):
191            set_trace_in_qt()
192            return self._original_run()
193
194    class RunnableWrapper(QtCore.QRunnable):  # Wrapper for QRunnable
195
196        def __init__(self, *args):
197            _original_runnable_init(self, *args)
198
199            self._original_run = self.run
200            self.run = self._new_run
201
202
203        def _new_run(self):
204            set_trace_in_qt()
205            return self._original_run()
206
207    QtCore.QThread = ThreadWrapper
208    QtCore.QRunnable = RunnableWrapper
209