From bbd3cf695c6f4244feb44b44f3232931bf7b6539 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 15 Jun 2020 15:02:02 -0700 Subject: [PATCH 1/4] Backport PR #15656: Support fractional HiDpi scaling with Qt backends Merge pull request #15656 from timhoffm/qt-fractional-hidpi Support fractional HiDpi scaling with Qt backends Conflicts: lib/matplotlib/backends/backend_qt5.py lib/matplotlib/backends/qt_compat.py - only backport relevant changes --- lib/matplotlib/backends/backend_qt5.py | 16 ++++----- lib/matplotlib/backends/backend_qt5agg.py | 6 ++-- lib/matplotlib/backends/backend_qt5cairo.py | 10 +++--- lib/matplotlib/backends/qt_compat.py | 36 ++++++++++++++++++++- lib/matplotlib/tests/test_backend_qt.py | 23 +++++++++++++ 5 files changed, 70 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 718c005f1f0d..4b1420f238a2 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -15,10 +15,12 @@ import matplotlib.backends.qt_editor.figureoptions as figureoptions from matplotlib.backends.qt_editor.formsubplottool import UiSubplotTool from matplotlib.backend_managers import ToolManager - +from . import qt_compat from .qt_compat import ( QtCore, QtGui, QtWidgets, _isdeleted, _getSaveFileName, - is_pyqt5, __version__, QT_API) + is_pyqt5, __version__, QT_API, + _devicePixelRatioF) + backend_version = __version__ @@ -267,12 +269,7 @@ def _update_figure_dpi(self): @property def _dpi_ratio(self): - # Not available on Qt4 or some older Qt5. - try: - # self.devicePixelRatio() returns 0 in rare cases - return self.devicePixelRatio() or 1 - except AttributeError: - return 1 + return _devicePixelRatioF(self) def _update_dpi(self): # As described in __init__ above, we need to be careful in cases with @@ -683,8 +680,7 @@ def _icon(self, name, color=None): if is_pyqt5(): name = name.replace('.png', '_large.png') pm = QtGui.QPixmap(os.path.join(self.basedir, name)) - if hasattr(pm, 'setDevicePixelRatio'): - pm.setDevicePixelRatio(self.canvas._dpi_ratio) + qt_compat._setDevicePixelRatioF(pm, _devicePixelRatioF(self)) if color is not None: mask = pm.createMaskFromColor(QtGui.QColor('black'), QtCore.Qt.MaskOutColor) diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index 09a2a261844d..56db30b335f1 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -11,7 +11,7 @@ from .backend_qt5 import ( QtCore, QtGui, QtWidgets, _BackendQT5, FigureCanvasQT, FigureManagerQT, NavigationToolbar2QT, backend_version) -from .qt_compat import QT_API +from .qt_compat import QT_API, _setDevicePixelRatioF class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT): @@ -64,9 +64,7 @@ def paintEvent(self, event): qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0], QtGui.QImage.Format_ARGB32_Premultiplied) - if hasattr(qimage, 'setDevicePixelRatio'): - # Not available on Qt4 or some older Qt5. - qimage.setDevicePixelRatio(self._dpi_ratio) + _setDevicePixelRatioF(qimage, self._dpi_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_qt5cairo.py b/lib/matplotlib/backends/backend_qt5cairo.py index 5a38a80864be..d29997410323 100644 --- a/lib/matplotlib/backends/backend_qt5cairo.py +++ b/lib/matplotlib/backends/backend_qt5cairo.py @@ -2,7 +2,7 @@ from .backend_cairo import cairo, FigureCanvasCairo, RendererCairo from .backend_qt5 import QtCore, QtGui, _BackendQT5, FigureCanvasQT -from .qt_compat import QT_API +from .qt_compat import QT_API, _setDevicePixelRatioF class FigureCanvasQTCairo(FigureCanvasQT, FigureCanvasCairo): @@ -19,8 +19,8 @@ def draw(self): def paintEvent(self, event): self._update_dpi() dpi_ratio = self._dpi_ratio - width = dpi_ratio * self.width() - height = dpi_ratio * self.height() + width = int(dpi_ratio * self.width()) + height = int(dpi_ratio * self.height()) if (width, height) != self._renderer.get_canvas_width_height(): surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) self._renderer.set_ctx_from_surface(surface) @@ -33,9 +33,7 @@ def paintEvent(self, event): # QImage under PySide on Python 3. if QT_API == 'PySide': ctypes.c_long.from_address(id(buf)).value = 1 - if hasattr(qimage, 'setDevicePixelRatio'): - # Not available on Qt4 or some older Qt5. - qimage.setDevicePixelRatio(dpi_ratio) + _setDevicePixelRatioF(qimage, dpi_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 2a4a3aa53629..90dddc29faa3 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -173,4 +173,38 @@ def is_pyqt5(): # These globals are only defined for backcompatibility purposes. ETS = dict(pyqt=(QT_API_PYQTv2, 4), pyside=(QT_API_PYSIDE, 4), pyqt5=(QT_API_PYQT5, 5), pyside2=(QT_API_PYSIDE2, 5)) -QT_RC_MAJOR_VERSION = 5 if is_pyqt5() else 4 + +QT_RC_MAJOR_VERSION = int(QtCore.qVersion().split(".")[0]) + + +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 Qt4 or some older Qt5. + # self.devicePixelRatio() returns 0 in rare cases + return obj.devicePixelRatio() or 1 + except AttributeError: + return 1 + + +def _setDevicePixelRatioF(obj, val): + """ + Call obj.setDevicePixelRatioF(val) with graceful fallback for older Qt. + + This can be replaced by the direct call when we require Qt>=5.6. + """ + if hasattr(obj, 'setDevicePixelRatioF'): + # Not available on Qt<5.6 + obj.setDevicePixelRatioF(val) + if hasattr(obj, 'setDevicePixelRatio'): + # Not available on Qt4 or some older Qt5. + obj.setDevicePixelRatio(val) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index e0c0a3b2010f..c49af2ddfbbc 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -253,6 +253,29 @@ def test_dpi_ratio_change(): assert qt_canvas.get_width_height() == (600, 240) assert (fig.get_size_inches() == (5, 2)).all() + p.return_value = 1.5 + + assert qt_canvas._dpi_ratio == 1.5 + + qt_canvas.draw() + qApp.processEvents() + # this second processEvents is required to fully run the draw. + # On `update` we notice the DPI has changed and trigger a + # resize event to refresh, the second processEvents is + # required to process that and fully update the window sizes. + qApp.processEvents() + + # The DPI and the renderer width/height change + assert fig.dpi == 180 + assert qt_canvas.renderer.width == 900 + assert qt_canvas.renderer.height == 360 + + # The actual widget size and figure physical size don't change + assert size.width() == 600 + assert size.height() == 240 + assert qt_canvas.get_width_height() == (600, 240) + assert (fig.get_size_inches() == (5, 2)).all() + @pytest.mark.backend('Qt5Agg') def test_subplottool(): From d91fa3a0315dce228b75a5845e04c214ee808dea Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 11 Jun 2020 17:53:05 -0400 Subject: [PATCH 2/4] Merge pull request #17618 from tacaswell/doc_event_loop Doc event loop --- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/backends/backend_qt5.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d9a4447744a0..ac19d29884c4 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2293,7 +2293,7 @@ def start_event_loop(self, timeout=0): The event loop blocks until a callback function triggers `stop_event_loop`, or *timeout* is reached. - If *timeout* is negative, never timeout. + If *timeout* is 0 or negative, never timeout. Only interactive backends need to reimplement this method and it relies on `flush_events` being properly implemented. diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 4b1420f238a2..6bfe20e87cac 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -451,7 +451,7 @@ def start_event_loop(self, timeout=0): if hasattr(self, "_event_loop") and self._event_loop.isRunning(): raise RuntimeError("Event loop already running") self._event_loop = event_loop = QtCore.QEventLoop() - if timeout: + if timeout > 0: timer = QtCore.QTimer.singleShot(timeout * 1000, event_loop.quit) event_loop.exec_() From c12c4ffb37043af4d24a574cd2b8494dec692e2a Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 15 Jun 2020 14:48:58 -0700 Subject: [PATCH 3/4] Backport PR #17600: FIX: work with PyQt 5.15 Merge pull request #17600 from tacaswell/mnt_more_qt515_fixes FIX: work with PyQt 5.15 Conflicts: lib/matplotlib/backends/backend_qt5.py - on this branch the blitting code is still in backend_qt5agg.py --- lib/matplotlib/backends/backend_qt5.py | 3 ++- lib/matplotlib/backends/backend_qt5agg.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 6bfe20e87cac..4aa804ad7e34 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -452,7 +452,8 @@ def start_event_loop(self, timeout=0): raise RuntimeError("Event loop already running") self._event_loop = event_loop = QtCore.QEventLoop() if timeout > 0: - timer = QtCore.QTimer.singleShot(timeout * 1000, event_loop.quit) + timer = QtCore.QTimer.singleShot(int(timeout * 1000), + event_loop.quit) event_loop.exec_() def stop_event_loop(self, event=None): diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index 56db30b335f1..f4d7842cbd1f 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -85,7 +85,7 @@ def blit(self, bbox=None): bbox = self.figure.bbox # repaint uses logical pixels, not physical pixels like the renderer. - l, b, w, h = [pt / self._dpi_ratio for pt in bbox.bounds] + l, b, w, h = [int(pt / self._dpi_ratio) for pt in bbox.bounds] t = b + h self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h) From ff32cc7428b8b5b8116a6890ebf3373bda264852 Mon Sep 17 00:00:00 2001 From: hannah Date: Tue, 16 Jun 2020 00:26:57 -0400 Subject: [PATCH 4/4] Backport PR #17640: More qt fractional DPI fixes Merge pull request #17640 from QuLogic/more-pyqt5-fixes More qt fractional DPI fixes --- lib/matplotlib/backends/backend_qt5.py | 4 ++-- lib/matplotlib/backends/qt_compat.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 4aa804ad7e34..dc4f6e26b066 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -18,7 +18,7 @@ from . import qt_compat from .qt_compat import ( QtCore, QtGui, QtWidgets, _isdeleted, _getSaveFileName, - is_pyqt5, __version__, QT_API, + is_pyqt5, __version__, QT_API, _setDevicePixelRatioF, _devicePixelRatioF) @@ -681,7 +681,7 @@ def _icon(self, name, color=None): if is_pyqt5(): name = name.replace('.png', '_large.png') pm = QtGui.QPixmap(os.path.join(self.basedir, name)) - qt_compat._setDevicePixelRatioF(pm, _devicePixelRatioF(self)) + _setDevicePixelRatioF(pm, _devicePixelRatioF(self)) if color is not None: mask = pm.createMaskFromColor(QtGui.QColor('black'), QtCore.Qt.MaskOutColor) diff --git a/lib/matplotlib/backends/qt_compat.py b/lib/matplotlib/backends/qt_compat.py index 90dddc29faa3..906845a68096 100644 --- a/lib/matplotlib/backends/qt_compat.py +++ b/lib/matplotlib/backends/qt_compat.py @@ -205,6 +205,6 @@ def _setDevicePixelRatioF(obj, val): if hasattr(obj, 'setDevicePixelRatioF'): # Not available on Qt<5.6 obj.setDevicePixelRatioF(val) - if hasattr(obj, 'setDevicePixelRatio'): + elif hasattr(obj, 'setDevicePixelRatio'): # Not available on Qt4 or some older Qt5. obj.setDevicePixelRatio(val) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy