##// END OF EJS Templates
STY: apply darker
Thomas A Caswell -
Show More
@@ -1,119 +1,129 b''
1 1 """ Import Qt in a manner suitable for an IPython kernel.
2 2
3 3 This is the import used for the `gui=qt` or `matplotlib=qt` initialization.
4 4
5 5 Import Priority:
6 6
7 7 if Qt has been imported anywhere else:
8 8 use that
9 9
10 10 if matplotlib has been imported and doesn't support v2 (<= 1.0.1):
11 11 use PyQt4 @v1
12 12
13 13 Next, ask QT_API env variable
14 14
15 15 if QT_API not set:
16 16 ask matplotlib what it's using. If Qt4Agg or Qt5Agg, then use the
17 17 version matplotlib is configured with
18 18
19 19 else: (matplotlib said nothing)
20 20 # this is the default path - nobody told us anything
21 21 try in this order:
22 22 PyQt default version, PySide, PyQt5
23 23 else:
24 24 use what QT_API says
25 25
26 26 """
27 27 # NOTE: This is no longer an external, third-party module, and should be
28 28 # considered part of IPython. For compatibility however, it is being kept in
29 29 # IPython/external.
30 30
31 31 import os
32 32 import sys
33 33
34 34 from IPython.utils.version import check_version
35 35 from IPython.external.qt_loaders import (
36 load_qt, loaded_api, enum_factory,
36 load_qt,
37 loaded_api,
38 enum_factory,
37 39 # QT6
38 QT_API_PYQT6, QT_API_PYSIDE6,
40 QT_API_PYQT6,
41 QT_API_PYSIDE6,
39 42 # QT5
40 QT_API_PYQT5, QT_API_PYSIDE2,
43 QT_API_PYQT5,
44 QT_API_PYSIDE2,
41 45 # QT4
42 QT_API_PYQTv1, QT_API_PYQT, QT_API_PYSIDE,
46 QT_API_PYQTv1,
47 QT_API_PYQT,
48 QT_API_PYSIDE,
43 49 # default
44 QT_API_PYQT_DEFAULT
50 QT_API_PYQT_DEFAULT,
45 51 )
46 52
47 53 _qt_apis = (
48 54 # QT6
49 QT_API_PYQT6, QT_API_PYSIDE6,
55 QT_API_PYQT6,
56 QT_API_PYSIDE6,
50 57 # QT5
51 QT_API_PYQT5, QT_API_PYSIDE2,
58 QT_API_PYQT5,
59 QT_API_PYSIDE2,
52 60 # QT4
53 QT_API_PYQTv1, QT_API_PYQT, QT_API_PYSIDE,
61 QT_API_PYQTv1,
62 QT_API_PYQT,
63 QT_API_PYSIDE,
54 64 # default
55 QT_API_PYQT_DEFAULT
65 QT_API_PYQT_DEFAULT,
56 66 )
57 67
58 68
59 69 def matplotlib_options(mpl):
60 70 """Constraints placed on an imported matplotlib."""
61 71 if mpl is None:
62 72 return
63 73 backend = mpl.rcParams.get('backend', None)
64 74 if backend == 'Qt4Agg':
65 75 mpqt = mpl.rcParams.get('backend.qt4', None)
66 76 if mpqt is None:
67 77 return None
68 78 if mpqt.lower() == 'pyside':
69 79 return [QT_API_PYSIDE]
70 80 elif mpqt.lower() == 'pyqt4':
71 81 return [QT_API_PYQT_DEFAULT]
72 82 elif mpqt.lower() == 'pyqt4v2':
73 83 return [QT_API_PYQT]
74 84 raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" %
75 85 mpqt)
76 86 elif backend == 'Qt5Agg':
77 87 mpqt = mpl.rcParams.get('backend.qt5', None)
78 88 if mpqt is None:
79 89 return None
80 90 if mpqt.lower() == 'pyqt5':
81 91 return [QT_API_PYQT5]
82 92 raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" %
83 93 mpqt)
84 94
85 95 def get_options():
86 96 """Return a list of acceptable QT APIs, in decreasing order of preference."""
87 97 #already imported Qt somewhere. Use that
88 98 loaded = loaded_api()
89 99 if loaded is not None:
90 100 return [loaded]
91 101
92 102 mpl = sys.modules.get('matplotlib', None)
93 103
94 104 if mpl is not None and not check_version(mpl.__version__, '1.0.2'):
95 105 #1.0.1 only supports PyQt4 v1
96 106 return [QT_API_PYQT_DEFAULT]
97 107
98 108 qt_api = os.environ.get('QT_API', None)
99 109 if qt_api is None:
100 110 #no ETS variable. Ask mpl, then use default fallback path
101 111 return matplotlib_options(mpl) or [
102 112 QT_API_PYQT_DEFAULT,
103 113 QT_API_PYQT6,
104 114 QT_API_PYSIDE6,
105 115 QT_API_PYQT5,
106 116 QT_API_PYSIDE2,
107 117 QT_API_PYQT,
108 QT_API_PYSIDE
118 QT_API_PYSIDE,
109 119 ]
110 120 elif qt_api not in _qt_apis:
111 121 raise RuntimeError("Invalid Qt API %r, valid values are: %r" %
112 122 (qt_api, ', '.join(_qt_apis)))
113 123 else:
114 124 return [qt_api]
115 125
116 126
117 127 api_opts = get_options()
118 128 QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts)
119 129 enum_helper = enum_factory(QT_API, QtCore)
@@ -1,398 +1,401 b''
1 1 """
2 2 This module contains factory functions that attempt
3 3 to return Qt submodules from the various python Qt bindings.
4 4
5 5 It also protects against double-importing Qt with different
6 6 bindings, which is unstable and likely to crash
7 7
8 8 This is used primarily by qt and qt_for_kernel, and shouldn't
9 9 be accessed directly from the outside
10 10 """
11 11 import sys
12 12 import types
13 13 from functools import partial, lru_cache
14 14 import operator
15 15
16 16 from IPython.utils.version import check_version
17 17
18 18 # ### Available APIs.
19 19 # Qt6
20 20 QT_API_PYQT6 = "pyqt6"
21 21 QT_API_PYSIDE6 = "pyside6"
22 22
23 23 # Qt5
24 24 QT_API_PYQT5 = 'pyqt5'
25 25 QT_API_PYSIDE2 = 'pyside2'
26 26
27 27 # Qt4
28 QT_API_PYQT = 'pyqt' # Force version 2
29 QT_API_PYQTv1 = 'pyqtv1' # Force version 2
30 QT_API_PYSIDE = 'pyside'
28 QT_API_PYQT = "pyqt" # Force version 2
29 QT_API_PYQTv1 = "pyqtv1" # Force version 2
30 QT_API_PYSIDE = "pyside"
31 31
32 QT_API_PYQT_DEFAULT = 'pyqtdefault' # use system default for version 1 vs. 2
32 QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2
33 33
34 34 api_to_module = {
35 35 # Qt6
36 36 QT_API_PYQT6: "PyQt6",
37 37 QT_API_PYSIDE6: "PySide6",
38 38 # Qt5
39 QT_API_PYQT5: 'PyQt5',
40 QT_API_PYSIDE2: 'PySide2',
39 QT_API_PYQT5: "PyQt5",
40 QT_API_PYSIDE2: "PySide2",
41 41 # Qt4
42 QT_API_PYSIDE: 'PySide',
43 QT_API_PYQT: 'PyQt4',
44 QT_API_PYQTv1: 'PyQt4',
42 QT_API_PYSIDE: "PySide",
43 QT_API_PYQT: "PyQt4",
44 QT_API_PYQTv1: "PyQt4",
45 45 # default
46 QT_API_PYQT_DEFAULT: 'PyQt6',
46 QT_API_PYQT_DEFAULT: "PyQt6",
47 47 }
48 48
49 49
50 50 class ImportDenier(object):
51 51 """Import Hook that will guard against bad Qt imports
52 52 once IPython commits to a specific binding
53 53 """
54 54
55 55 def __init__(self):
56 56 self.__forbidden = set()
57 57
58 58 def forbid(self, module_name):
59 59 sys.modules.pop(module_name, None)
60 60 self.__forbidden.add(module_name)
61 61
62 62 def find_module(self, fullname, path=None):
63 63 if path:
64 64 return
65 65 if fullname in self.__forbidden:
66 66 return self
67 67
68 68 def load_module(self, fullname):
69 69 raise ImportError("""
70 70 Importing %s disabled by IPython, which has
71 71 already imported an Incompatible QT Binding: %s
72 72 """ % (fullname, loaded_api()))
73 73
74 74
75 75 ID = ImportDenier()
76 76 sys.meta_path.insert(0, ID)
77 77
78 78
79 79 def commit_api(api):
80 80 """Commit to a particular API, and trigger ImportErrors on subsequent
81 81 dangerous imports"""
82 82 modules = set(api_to_module.values())
83 83
84 84 modules.remove(api_to_module[api])
85 85 for mod in modules:
86 86 ID.forbid(mod)
87 87
88 88
89 89 def loaded_api():
90 90 """Return which API is loaded, if any
91 91
92 92 If this returns anything besides None,
93 93 importing any other Qt binding is unsafe.
94 94
95 95 Returns
96 96 -------
97 97 None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1'
98 98 """
99 99 if sys.modules.get("PyQt6.QtCore"):
100 100 return QT_API_PYQT6
101 101 elif sys.modules.get("PySide6.QtCore"):
102 102 return QT_API_PYSIDE6
103 103 elif sys.modules.get("PyQt5.QtCore"):
104 104 return QT_API_PYQT5
105 105 elif sys.modules.get("PySide2.QtCore"):
106 106 return QT_API_PYSIDE2
107 elif sys.modules.get('PyQt4.QtCore'):
107 elif sys.modules.get("PyQt4.QtCore"):
108 108 if qtapi_version() == 2:
109 109 return QT_API_PYQT
110 110 else:
111 111 return QT_API_PYQTv1
112 elif sys.modules.get('PySide.QtCore'):
112 elif sys.modules.get("PySide.QtCore"):
113 113 return QT_API_PYSIDE
114 114
115 115 return None
116 116
117 117
118 118 def has_binding(api):
119 119 """Safely check for PyQt4/5, PySide or PySide2, without importing submodules
120 120
121 121 Parameters
122 122 ----------
123 123 api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault']
124 124 Which module to check for
125 125
126 126 Returns
127 127 -------
128 128 True if the relevant module appears to be importable
129 129 """
130 130 module_name = api_to_module[api]
131 131 from importlib.util import find_spec
132 132
133 133 required = ['QtCore', 'QtGui', 'QtSvg']
134 134 if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6):
135 135 # QT5 requires QtWidgets too
136 136 required.append('QtWidgets')
137 137
138 138 for submod in required:
139 139 try:
140 140 spec = find_spec('%s.%s' % (module_name, submod))
141 141 except ImportError:
142 142 # Package (e.g. PyQt5) not found
143 143 return False
144 144 else:
145 145 if spec is None:
146 146 # Submodule (e.g. PyQt5.QtCore) not found
147 147 return False
148 148
149 149 if api == QT_API_PYSIDE:
150 150 # We can also safely check PySide version
151 151 import PySide
152 152 return check_version(PySide.__version__, '1.0.3')
153 153
154 154 return True
155 155
156 156
157 157 def qtapi_version():
158 158 """Return which QString API has been set, if any
159 159
160 160 Returns
161 161 -------
162 162 The QString API version (1 or 2), or None if not set
163 163 """
164 164 try:
165 165 import sip
166 166 except ImportError:
167 167 # as of PyQt5 5.11, sip is no longer available as a top-level
168 168 # module and needs to be imported from the PyQt5 namespace
169 169 try:
170 170 from PyQt5 import sip
171 171 except ImportError:
172 172 return
173 173 try:
174 174 return sip.getapi('QString')
175 175 except ValueError:
176 176 return
177 177
178 178
179 179 def can_import(api):
180 180 """Safely query whether an API is importable, without importing it"""
181 181 if not has_binding(api):
182 182 return False
183 183
184 184 current = loaded_api()
185 185 if api == QT_API_PYQT_DEFAULT:
186 186 return current in [QT_API_PYQT6, None]
187 187 else:
188 188 return current in [api, None]
189 189
190 190
191 191 def import_pyqt4(version=2):
192 192 """
193 193 Import PyQt4
194 194
195 195 Parameters
196 196 ----------
197 197 version : 1, 2, or None
198 198 Which QString/QVariant API to use. Set to None to use the system
199 199 default
200 200
201 201 ImportErrors rasied within this function are non-recoverable
202 202 """
203 203 # The new-style string API (version=2) automatically
204 204 # converts QStrings to Unicode Python strings. Also, automatically unpacks
205 205 # QVariants to their underlying objects.
206 206 import sip
207 207
208 208 if version is not None:
209 209 sip.setapi('QString', version)
210 210 sip.setapi('QVariant', version)
211 211
212 212 from PyQt4 import QtGui, QtCore, QtSvg
213 213
214 214 if not check_version(QtCore.PYQT_VERSION_STR, '4.7'):
215 215 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
216 216 QtCore.PYQT_VERSION_STR)
217 217
218 218 # Alias PyQt-specific functions for PySide compatibility.
219 219 QtCore.Signal = QtCore.pyqtSignal
220 220 QtCore.Slot = QtCore.pyqtSlot
221 221
222 222 # query for the API version (in case version == None)
223 223 version = sip.getapi('QString')
224 224 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
225 225 return QtCore, QtGui, QtSvg, api
226 226
227 227
228 228 def import_pyqt5():
229 229 """
230 230 Import PyQt5
231 231
232 232 ImportErrors rasied within this function are non-recoverable
233 233 """
234 234
235 235 from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui
236 236
237 237 # Alias PyQt-specific functions for PySide compatibility.
238 238 QtCore.Signal = QtCore.pyqtSignal
239 239 QtCore.Slot = QtCore.pyqtSlot
240 240
241 241 # Join QtGui and QtWidgets for Qt4 compatibility.
242 242 QtGuiCompat = types.ModuleType('QtGuiCompat')
243 243 QtGuiCompat.__dict__.update(QtGui.__dict__)
244 244 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
245 245
246 246 api = QT_API_PYQT5
247 247 return QtCore, QtGuiCompat, QtSvg, api
248 248
249
249 250 def import_pyqt6():
250 251 """
251 252 Import PyQt6
252 253
253 254 ImportErrors rasied within this function are non-recoverable
254 255 """
255 256
256 257 from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui
257 258
258 259 # Alias PyQt-specific functions for PySide compatibility.
259 260 QtCore.Signal = QtCore.pyqtSignal
260 261 QtCore.Slot = QtCore.pyqtSlot
261 262
262 263 # Join QtGui and QtWidgets for Qt4 compatibility.
263 QtGuiCompat = types.ModuleType('QtGuiCompat')
264 QtGuiCompat = types.ModuleType("QtGuiCompat")
264 265 QtGuiCompat.__dict__.update(QtGui.__dict__)
265 266 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
266 267
267 268 api = QT_API_PYQT6
268 269 return QtCore, QtGuiCompat, QtSvg, api
269 270
270 271
271 272 def import_pyside():
272 273 """
273 274 Import PySide
274 275
275 276 ImportErrors raised within this function are non-recoverable
276 277 """
277 278 from PySide import QtGui, QtCore, QtSvg
278 279 return QtCore, QtGui, QtSvg, QT_API_PYSIDE
279 280
280 281 def import_pyside2():
281 282 """
282 283 Import PySide2
283 284
284 285 ImportErrors raised within this function are non-recoverable
285 286 """
286 287 from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
287 288
288 289 # Join QtGui and QtWidgets for Qt4 compatibility.
289 290 QtGuiCompat = types.ModuleType('QtGuiCompat')
290 291 QtGuiCompat.__dict__.update(QtGui.__dict__)
291 292 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
292 293 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
293 294
294 295 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2
295 296
297
296 298 def import_pyside6():
297 299 """
298 300 Import PySide6
299 301
300 302 ImportErrors raised within this function are non-recoverable
301 303 """
302 304 from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
303 305
304 306 # Join QtGui and QtWidgets for Qt4 compatibility.
305 QtGuiCompat = types.ModuleType('QtGuiCompat')
307 QtGuiCompat = types.ModuleType("QtGuiCompat")
306 308 QtGuiCompat.__dict__.update(QtGui.__dict__)
307 309 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
308 310 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
309 311
310 312 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6
311 313
312 314
313 315 def load_qt(api_options):
314 316 """
315 317 Attempt to import Qt, given a preference list
316 318 of permissible bindings
317 319
318 320 It is safe to call this function multiple times.
319 321
320 322 Parameters
321 323 ----------
322 324 api_options: List of strings
323 325 The order of APIs to try. Valid items are 'pyside', 'pyside2',
324 326 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault'
325 327
326 328 Returns
327 329 -------
328 330
329 331 A tuple of QtCore, QtGui, QtSvg, QT_API
330 332 The first three are the Qt modules. The last is the
331 333 string indicating which module was loaded.
332 334
333 335 Raises
334 336 ------
335 337 ImportError, if it isn't possible to import any requested
336 338 bindings (either because they aren't installed, or because
337 339 an incompatible library has already been installed)
338 340 """
339 341 loaders = {
340 342 # Qt6
341 343 QT_API_PYQT6: import_pyqt6,
342 344 QT_API_PYSIDE6: import_pyside6,
343 345 # Qt5
344 346 QT_API_PYQT5: import_pyqt5,
345 347 QT_API_PYSIDE2: import_pyside2,
346 348 # Qt4
347 349 QT_API_PYSIDE: import_pyside,
348 350 QT_API_PYQT: import_pyqt4,
349 351 QT_API_PYQTv1: partial(import_pyqt4, version=1),
350 352 # default
351 353 QT_API_PYQT_DEFAULT: import_pyqt6,
352 354 }
353 355
354 356 for api in api_options:
355 357
356 358 if api not in loaders:
357 359 raise RuntimeError(
358 360 "Invalid Qt API %r, valid values are: %s" %
359 361 (api, ", ".join(["%r" % k for k in loaders.keys()])))
360 362
361 363 if not can_import(api):
362 364 continue
363 365
364 366 #cannot safely recover from an ImportError during this
365 367 result = loaders[api]()
366 368 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
367 369 commit_api(api)
368 370 return result
369 371 else:
370 372 raise ImportError("""
371 373 Could not load requested Qt binding. Please ensure that
372 374 PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available,
373 375 and only one is imported per session.
374 376
375 377 Currently-imported Qt library: %r
376 378 PyQt4 available (requires QtCore, QtGui, QtSvg): %s
377 379 PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
378 380 PySide >= 1.0.3 installed: %s
379 381 PySide2 installed: %s
380 382 Tried to load: %r
381 383 """ % (loaded_api(),
382 384 has_binding(QT_API_PYQT),
383 385 has_binding(QT_API_PYQT5),
384 386 has_binding(QT_API_PYSIDE),
385 387 has_binding(QT_API_PYSIDE2),
386 388 api_options))
387 389
388 390
389 391 def enum_factory(QT_API, QtCore):
390 392 """Construct an enum helper to account for PyQt5 <-> PyQt6 changes."""
393
391 394 @lru_cache(None)
392 395 def _enum(name):
393 396 # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6).
394 397 return operator.attrgetter(
395 398 name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0]
396 399 )(sys.modules[QtCore.__package__])
397 400
398 401 return _enum
@@ -1,55 +1,61 b''
1 1 import importlib
2 2 import os
3 3
4 4 aliases = {
5 5 'qt4': 'qt',
6 6 'gtk2': 'gtk',
7 7 }
8 8
9 9 backends = [
10 'qt', 'qt4', 'qt5', 'qt6',
11 'gtk', 'gtk2', 'gtk3',
12 'tk',
13 'wx',
14 'pyglet', 'glut',
15 'osx',
16 'asyncio'
10 "qt",
11 "qt4",
12 "qt5",
13 "qt6",
14 "gtk",
15 "gtk2",
16 "gtk3",
17 "tk",
18 "wx",
19 "pyglet",
20 "glut",
21 "osx",
22 "asyncio",
17 23 ]
18 24
19 25 registered = {}
20 26
21 27 def register(name, inputhook):
22 28 """Register the function *inputhook* as an event loop integration."""
23 29 registered[name] = inputhook
24 30
25 31
26 32 class UnknownBackend(KeyError):
27 33 def __init__(self, name):
28 34 self.name = name
29 35
30 36 def __str__(self):
31 37 return ("No event loop integration for {!r}. "
32 38 "Supported event loops are: {}").format(self.name,
33 39 ', '.join(backends + sorted(registered)))
34 40
35 41
36 42 def get_inputhook_name_and_func(gui):
37 43 if gui in registered:
38 44 return gui, registered[gui]
39 45
40 46 if gui not in backends:
41 47 raise UnknownBackend(gui)
42 48
43 49 if gui in aliases:
44 50 return get_inputhook_name_and_func(aliases[gui])
45 51
46 52 gui_mod = gui
47 if gui == 'qt5':
48 os.environ['QT_API'] = 'pyqt5'
49 gui_mod = 'qt'
50 elif gui == 'qt6':
51 os.environ['QT_API'] = 'pyqt6'
52 gui_mod = 'qt'
53 if gui == "qt5":
54 os.environ["QT_API"] = "pyqt5"
55 gui_mod = "qt"
56 elif gui == "qt6":
57 os.environ["QT_API"] = "pyqt6"
58 gui_mod = "qt"
53 59
54 60 mod = importlib.import_module('IPython.terminal.pt_inputhooks.'+gui_mod)
55 61 return gui, mod.inputhook
@@ -1,86 +1,85 b''
1 1 import sys
2 2 import os
3 3 from IPython.external.qt_for_kernel import QtCore, QtGui, enum_helper
4 4 from IPython import get_ipython
5 5
6 6 # If we create a QApplication, keep a reference to it so that it doesn't get
7 7 # garbage collected.
8 8 _appref = None
9 9 _already_warned = False
10 10
11 11
12 12 def _exec(obj):
13 13 # exec on PyQt6, exec_ elsewhere.
14 14 obj.exec() if hasattr(obj, "exec") else obj.exec_()
15 15
16 16
17 17 def _reclaim_excepthook():
18 18 shell = get_ipython()
19 19 if shell is not None:
20 20 sys.excepthook = shell.excepthook
21 21
22 22
23 23 def inputhook(context):
24 24 global _appref
25 25 app = QtCore.QCoreApplication.instance()
26 26 if not app:
27 27 if sys.platform == 'linux':
28 28 if not os.environ.get('DISPLAY') \
29 29 and not os.environ.get('WAYLAND_DISPLAY'):
30 30 import warnings
31 31 global _already_warned
32 32 if not _already_warned:
33 33 _already_warned = True
34 34 warnings.warn(
35 35 'The DISPLAY or WAYLAND_DISPLAY environment variable is '
36 36 'not set or empty and Qt5 requires this environment '
37 37 'variable. Deactivate Qt5 code.'
38 38 )
39 39 return
40 40 try:
41 QtCore.QApplication.setAttribute(
42 QtCore.Qt.AA_EnableHighDpiScaling)
41 QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
43 42 except AttributeError: # Only for Qt>=5.6, <6.
44 43 pass
45 44 try:
46 45 QtCore.QApplication.setHighDpiScaleFactorRoundingPolicy(
47 QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
46 QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
47 )
48 48 except AttributeError: # Only for Qt>=5.14.
49 49 pass
50 50 _appref = app = QtGui.QApplication([" "])
51 51
52 52 # "reclaim" IPython sys.excepthook after event loop starts
53 53 # without this, it defaults back to BaseIPythonApplication.excepthook
54 54 # and exceptions in the Qt event loop are rendered without traceback
55 55 # formatting and look like "bug in IPython".
56 56 QtCore.QTimer.singleShot(0, _reclaim_excepthook)
57 57
58 58 event_loop = QtCore.QEventLoop(app)
59 59
60 60 if sys.platform == 'win32':
61 61 # The QSocketNotifier method doesn't appear to work on Windows.
62 62 # Use polling instead.
63 63 timer = QtCore.QTimer()
64 64 timer.timeout.connect(event_loop.quit)
65 65 while not context.input_is_ready():
66 66 timer.start(50) # 50 ms
67 67 event_loop.exec_()
68 68 timer.stop()
69 69 else:
70 70 # On POSIX platforms, we can use a file descriptor to quit the event
71 71 # loop when there is input ready to read.
72 72 notifier = QtCore.QSocketNotifier(
73 context.fileno(),
74 enum_helper('QtCore.QSocketNotifier.Type').Read
73 context.fileno(), enum_helper("QtCore.QSocketNotifier.Type").Read
75 74 )
76 75 try:
77 76 # connect the callback we care about before we turn it on
78 77 # lambda is necessary as PyQT inspect the function signature to know
79 78 # what arguments to pass to. See https://github.com/ipython/ipython/pull/12355
80 79 notifier.activated.connect(lambda: event_loop.exit())
81 80 notifier.setEnabled(True)
82 81 # only start the event loop we are not already flipped
83 82 if not context.input_is_ready():
84 83 _exec(event_loop)
85 84 finally:
86 85 notifier.setEnabled(False)
General Comments 0
You need to be logged in to leave comments. Login now