|
| 1 | +""" |
| 2 | +Qt binding and backend selector. |
| 3 | +
|
| 4 | +The selection logic is as follows: |
| 5 | +- if any of PyQt6, PySide6, PyQt5, or PySide2 have already been |
| 6 | + imported (checked in that order), use it; |
| 7 | +- otherwise, if the QT_API environment variable (used by Enthought) is set, use |
| 8 | + it to determine which binding to use; |
| 9 | +
|
| 10 | +[taken from matplotlib] |
| 11 | +""" |
| 12 | + |
| 13 | +import operator |
| 14 | +import os |
| 15 | +import platform |
| 16 | +import sys |
| 17 | + |
| 18 | +from packaging.version import parse as parse_version |
| 19 | + |
| 20 | +QT_API_PYQT6 = "PyQt6" |
| 21 | +QT_API_PYSIDE6 = "PySide6" |
| 22 | +QT_API_PYQT5 = "PyQt5" |
| 23 | +QT_API_PYSIDE2 = "PySide2" |
| 24 | +QT_API_ENV = os.environ.get("QT_API") |
| 25 | +if QT_API_ENV is not None: |
| 26 | + QT_API_ENV = QT_API_ENV.lower() |
| 27 | +_ETS = { # Mapping of QT_API_ENV to requested binding. |
| 28 | + "pyqt6": QT_API_PYQT6, |
| 29 | + "pyside6": QT_API_PYSIDE6, |
| 30 | + "pyqt5": QT_API_PYQT5, |
| 31 | + "pyside2": QT_API_PYSIDE2, |
| 32 | +} |
| 33 | +# First, check if anything is already imported. |
| 34 | +if sys.modules.get("PyQt6.QtCore"): |
| 35 | + QT_API = QT_API_PYQT6 |
| 36 | +elif sys.modules.get("PySide6.QtCore"): |
| 37 | + QT_API = QT_API_PYSIDE6 |
| 38 | +elif sys.modules.get("PyQt5.QtCore"): |
| 39 | + QT_API = QT_API_PYQT5 |
| 40 | +elif sys.modules.get("PySide2.QtCore"): |
| 41 | + QT_API = QT_API_PYSIDE2 |
| 42 | +# A non-Qt backend was selected but we still got there (possible, e.g., when |
| 43 | +# fully manually embedding Matplotlib in a Qt app without using pyplot). |
| 44 | +elif QT_API_ENV is None: |
| 45 | + QT_API = None |
| 46 | +elif QT_API_ENV in _ETS: |
| 47 | + QT_API = _ETS[QT_API_ENV] |
| 48 | +else: |
| 49 | + raise RuntimeError( |
| 50 | + "The environment variable QT_API has the unrecognized value {!r}; " |
| 51 | + "valid values are {}".format(QT_API_ENV, ", ".join(_ETS)) |
| 52 | + ) |
| 53 | + |
| 54 | + |
| 55 | +def _setup_pyqt5plus(): |
| 56 | + global QtCore, QtWidgets, QtGui, __version__ |
| 57 | + global _to_int |
| 58 | + |
| 59 | + if QT_API == QT_API_PYQT6: |
| 60 | + from PyQt6 import QtCore, QtWidgets, QtGui |
| 61 | + |
| 62 | + __version__ = QtCore.PYQT_VERSION_STR |
| 63 | + _to_int = operator.attrgetter("value") |
| 64 | + elif QT_API == QT_API_PYSIDE6: |
| 65 | + from PySide6 import QtCore, QtWidgets, QtGui, __version__ |
| 66 | + |
| 67 | + if parse_version(__version__) >= parse_version("6.4"): |
| 68 | + _to_int = operator.attrgetter("value") |
| 69 | + else: |
| 70 | + _to_int = int |
| 71 | + elif QT_API == QT_API_PYQT5: |
| 72 | + from PyQt5 import QtCore, QtWidgets, QtGui |
| 73 | + |
| 74 | + __version__ = QtCore.PYQT_VERSION_STR |
| 75 | + _to_int = int |
| 76 | + elif QT_API == QT_API_PYSIDE2: |
| 77 | + from PySide2 import QtCore, QtWidgets, QtGui, __version__ |
| 78 | + |
| 79 | + _to_int = int |
| 80 | + else: |
| 81 | + raise AssertionError(f"Unexpected QT_API: {QT_API}") |
| 82 | + |
| 83 | + |
| 84 | +if QT_API in [QT_API_PYQT6, QT_API_PYQT5, QT_API_PYSIDE6, QT_API_PYSIDE2]: |
| 85 | + _setup_pyqt5plus() |
| 86 | +elif QT_API is None: # See above re: dict.__getitem__. |
| 87 | + _candidates = [ |
| 88 | + (_setup_pyqt5plus, QT_API_PYQT6), |
| 89 | + (_setup_pyqt5plus, QT_API_PYSIDE6), |
| 90 | + (_setup_pyqt5plus, QT_API_PYQT5), |
| 91 | + (_setup_pyqt5plus, QT_API_PYSIDE2), |
| 92 | + ] |
| 93 | + for _setup, QT_API in _candidates: |
| 94 | + try: |
| 95 | + _setup() |
| 96 | + except ImportError: |
| 97 | + continue |
| 98 | + break |
| 99 | + else: |
| 100 | + raise ImportError( |
| 101 | + "Failed to import any of the following Qt binding modules: {}".format( |
| 102 | + ", ".join([QT_API for _, QT_API in _candidates]) |
| 103 | + ) |
| 104 | + ) |
| 105 | +else: # We should not get there. |
| 106 | + raise AssertionError(f"Unexpected QT_API: {QT_API}") |
| 107 | +_version_info = tuple(QtCore.QLibraryInfo.version().segments()) |
| 108 | + |
| 109 | + |
| 110 | +if _version_info < (5, 12): |
| 111 | + raise ImportError( |
| 112 | + f"The Qt version imported is " |
| 113 | + f"{QtCore.QLibraryInfo.version().toString()} but Matplotlib requires " |
| 114 | + f"Qt>=5.12" |
| 115 | + ) |
| 116 | + |
| 117 | + |
| 118 | +# Fixes issues with Big Sur |
| 119 | +# https://bugreports.qt.io/browse/QTBUG-87014, fixed in qt 5.15.2 |
| 120 | +if ( |
| 121 | + sys.platform == "darwin" |
| 122 | + and parse_version(platform.mac_ver()[0]) >= parse_version("10.16") |
| 123 | + and _version_info < (5, 15, 2) |
| 124 | +): |
| 125 | + os.environ.setdefault("QT_MAC_WANTS_LAYER", "1") |
0 commit comments