From 3effb11ec9dc92f0454598fe196e6b1e524b6716 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 13 Dec 2022 11:38:02 +0100 Subject: [PATCH] Drop support for Qt<5.10. ... and update GUI minver policy. --- .../next_api_changes/development/24710-AL.rst | 4 ++ doc/devel/min_dep_policy.rst | 4 ++ lib/matplotlib/backends/backend_qt.py | 10 ++--- lib/matplotlib/backends/backend_qtagg.py | 15 ++----- lib/matplotlib/backends/backend_qtcairo.py | 9 ++-- lib/matplotlib/backends/qt_compat.py | 41 ++++--------------- 6 files changed, 30 insertions(+), 53 deletions(-) create mode 100644 doc/api/next_api_changes/development/24710-AL.rst diff --git a/doc/api/next_api_changes/development/24710-AL.rst b/doc/api/next_api_changes/development/24710-AL.rst new file mode 100644 index 000000000000..2f26f494a1ed --- /dev/null +++ b/doc/api/next_api_changes/development/24710-AL.rst @@ -0,0 +1,4 @@ +Support for Qt<5.10 has been dropped +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... as there are no wheels or conda packages that support both Qt 5.9 (or +older) and Python 3.8 (or newer). diff --git a/doc/devel/min_dep_policy.rst b/doc/devel/min_dep_policy.rst index 6f0ec95c7969..c387cb0a961d 100644 --- a/doc/devel/min_dep_policy.rst +++ b/doc/devel/min_dep_policy.rst @@ -71,6 +71,10 @@ Ghostscript, FFmpeg) support as old as practical. These can be difficult to install for end-users and we want to be usable on as many systems as possible. We will bump these on a case-by-case basis. +In the case of GUI frameworks for which we rely on Python bindings being +available, we will also drop support for bindings so old that they don't +support any Python version that we support. + .. _list-of-dependency-min-versions: List of dependency versions diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index c19cc240ba48..83d8dba01c30 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -14,9 +14,7 @@ from . import qt_compat from .qt_compat import ( QtCore, QtGui, QtWidgets, __version__, QT_API, - _enum, _to_int, - _devicePixelRatioF, _isdeleted, _setDevicePixelRatio, - _maybe_allow_interrupt + _enum, _to_int, _isdeleted, _maybe_allow_interrupt ) @@ -220,7 +218,8 @@ def __init__(self, figure=None): self.setPalette(palette) def _update_pixel_ratio(self): - if self._set_device_pixel_ratio(_devicePixelRatioF(self)): + if self._set_device_pixel_ratio( + self.devicePixelRatioF() or 1): # rarely, devicePixelRatioF=0 # The easiest way to resize the canvas is to emit a resizeEvent # since we implement all the logic for resizing the canvas for # that event. @@ -677,7 +676,8 @@ def _icon(self, name): filename = str(path_large if path_large.exists() else path_regular) pm = QtGui.QPixmap(filename) - _setDevicePixelRatio(pm, _devicePixelRatioF(self)) + pm.setDevicePixelRatio( + self.devicePixelRatioF() or 1) # rarely, devicePixelRatioF=0 if self.palette().color(self.backgroundRole()).value() < 128: icon_color = self.palette().color(self.foregroundRole()) mask = pm.createMaskFromColor( diff --git a/lib/matplotlib/backends/backend_qtagg.py b/lib/matplotlib/backends/backend_qtagg.py index 5804b433490a..f64264d712f7 100644 --- a/lib/matplotlib/backends/backend_qtagg.py +++ b/lib/matplotlib/backends/backend_qtagg.py @@ -6,8 +6,7 @@ from matplotlib.transforms import Bbox -from .qt_compat import QT_API, _enum, _setDevicePixelRatio -from .. import cbook +from .qt_compat import QT_API, _enum from .backend_agg import FigureCanvasAgg from .backend_qt import QtCore, QtGui, _BackendQT, FigureCanvasQT from .backend_qt import ( # noqa: F401 # pylint: disable=W0611 @@ -50,13 +49,6 @@ def paintEvent(self, event): bbox = Bbox([[left, bottom], [right, top]]) buf = memoryview(self.copy_from_bbox(bbox)) - fmts = _enum("QtGui.QImage.Format") - if hasattr(fmts, "Format_RGBA8888"): - fmt = fmts.Format_RGBA8888 - else: # Qt<=5.1 support. - fmt = fmts.Format_ARGB32_Premultiplied - buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(buf) - if QT_API == "PyQt6": from PyQt6 import sip ptr = int(sip.voidptr(buf)) @@ -64,8 +56,9 @@ def paintEvent(self, event): ptr = buf painter.eraseRect(rect) # clear the widget canvas - qimage = QtGui.QImage(ptr, buf.shape[1], buf.shape[0], fmt) - _setDevicePixelRatio(qimage, self.device_pixel_ratio) + qimage = QtGui.QImage(ptr, buf.shape[1], buf.shape[0], + _enum("QtGui.QImage.Format").Format_RGBA8888) + qimage.setDevicePixelRatio(self.device_pixel_ratio) # set origin using original QT coordinates origin = QtCore.QPoint(rect.left(), rect.top()) painter.drawImage(origin, qimage) diff --git a/lib/matplotlib/backends/backend_qtcairo.py b/lib/matplotlib/backends/backend_qtcairo.py index f6064285df5b..cca1be012f9e 100644 --- a/lib/matplotlib/backends/backend_qtcairo.py +++ b/lib/matplotlib/backends/backend_qtcairo.py @@ -2,7 +2,7 @@ from .backend_cairo import cairo, FigureCanvasCairo from .backend_qt import QtCore, QtGui, _BackendQT, FigureCanvasQT -from .qt_compat import QT_API, _enum, _setDevicePixelRatio +from .qt_compat import QT_API, _enum class FigureCanvasQTCairo(FigureCanvasCairo, FigureCanvasQT): @@ -31,10 +31,9 @@ def paintEvent(self, event): _enum("QtGui.QImage.Format").Format_ARGB32_Premultiplied) # Adjust the buf reference count to work around a memory leak bug in # QImage under PySide. - if QT_API in ('PySide', 'PySide2'): - if QtCore.__version_info__ < (5, 12): - ctypes.c_long.from_address(id(buf)).value = 1 - _setDevicePixelRatio(qimage, self.device_pixel_ratio) + if QT_API == "PySide2" and QtCore.__version_info__ < (5, 12): + ctypes.c_long.from_address(id(buf)).value = 1 + qimage.setDevicePixelRatio(self.device_pixel_ratio) painter = QtGui.QPainter(self) painter.eraseRect(event.rect()) painter.drawImage(0, 0, qimage) diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index 885cb1d118d7..663671894a74 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -125,7 +125,6 @@ def _isdeleted(obj): (_setup_pyqt5plus, QT_API_PYQT5), (_setup_pyqt5plus, QT_API_PYSIDE2), ] - for _setup, QT_API in _candidates: try: _setup() @@ -138,13 +137,21 @@ def _isdeleted(obj): .format(", ".join(_ETS.values()))) else: # We should not get there. raise AssertionError(f"Unexpected QT_API: {QT_API}") +_version_info = tuple(QtCore.QLibraryInfo.version().segments()) + + +if _version_info < (5, 10): + raise ImportError( + f"The Qt version imported is " + f"{QtCore.QLibraryInfo.version().toString()} but Matplotlib requires " + f"Qt>=5.10") # Fixes issues with Big Sur # https://bugreports.qt.io/browse/QTBUG-87014, fixed in qt 5.15.2 if (sys.platform == 'darwin' and parse_version(platform.mac_ver()[0]) >= parse_version("10.16") and - QtCore.QLibraryInfo.version().segments() <= [5, 15, 2]): + _version_info < (5, 15, 2)): os.environ.setdefault("QT_MAC_WANTS_LAYER", "1") @@ -167,36 +174,6 @@ def _exec(obj): obj.exec() if hasattr(obj, "exec") else obj.exec_() -def _devicePixelRatioF(obj): - """ - Return obj.devicePixelRatioF() with graceful fallback for older Qt. - - This can be replaced by the direct call when we require Qt>=5.6. - """ - try: - # Not available on Qt<5.6 - return obj.devicePixelRatioF() or 1 - except AttributeError: - pass - try: - # Not available on older Qt5. - # self.devicePixelRatio() returns 0 in rare cases - return obj.devicePixelRatio() or 1 - except AttributeError: - return 1 - - -def _setDevicePixelRatio(obj, val): - """ - Call obj.setDevicePixelRatio(val) with graceful fallback for older Qt. - - This can be replaced by the direct call when we require Qt>=5.6. - """ - if hasattr(obj, 'setDevicePixelRatio'): - # Not available on older Qt5. - obj.setDevicePixelRatio(val) - - @contextlib.contextmanager def _maybe_allow_interrupt(qapp): """