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