From 7ff959cc1acc812ae5d951b6281aa598c6f98999 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 18 Sep 2017 15:49:11 -0400 Subject: [PATCH 1/5] FIX: qt recursive draw - put draw_idle in the base canvas resize event - re-order our resize handling and Qt's resize handling (let Qt go first) - do not call processEvents from inside of the paint event (this maybe the critical fix) --- lib/matplotlib/backend_bases.py | 1 + lib/matplotlib/backends/backend_qt5.py | 5 +++-- lib/matplotlib/backends/backend_qt5agg.py | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 2bb642acf5a0..cf7889678553 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1845,6 +1845,7 @@ def resize_event(self): s = 'resize_event' event = ResizeEvent(s, self) self.callbacks.process(s, event) + self.draw_idle() def close_event(self, guiEvent=None): """Pass a `CloseEvent` to all functions connected to ``close_event``. diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 2e895195f906..eb2248f69fa0 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -359,9 +359,10 @@ def resizeEvent(self, event): winch = w / dpival hinch = h / dpival self.figure.set_size_inches(winch, hinch, forward=False) - FigureCanvasBase.resize_event(self) - self.draw_idle() + # pass back into Qt to let it finish QtWidgets.QWidget.resizeEvent(self, event) + # emit our resize events + FigureCanvasBase.resize_event(self) def sizeHint(self): w, h = self.get_width_height() diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index bb5bd64396c7..a4163fe3e1ae 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -72,7 +72,6 @@ def paintEvent(self, e): # since the latter doesn't guarantee that the event will be emitted # straight away, and this causes visual delays in the changes. self.resizeEvent(event) - QtWidgets.QApplication.instance().processEvents() # resizeEvent triggers a paintEvent itself, so we exit this one. return From 884334de7c902bffa0d9f53d08aedbcb39eafc9c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 21 Sep 2017 21:40:18 -0400 Subject: [PATCH 2/5] TST: add extra processEvents call in test The resizeEvent call does put another paint onto the event queue, however it seems that processEvents only processes events on the queue when it is called (not events added while it is processing). This is not clearly documented, but makes sense as this method is for keeping GUIs responsive during otherwise long-running tasks so it should not be able to extend it's run indefinitely by populating the queue. This is required because a previous commit removed the explicit processEvents call in paintEvent as it would occasionally cause recursive painting which may kill Qt. --- lib/matplotlib/tests/test_backend_qt5.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/tests/test_backend_qt5.py b/lib/matplotlib/tests/test_backend_qt5.py index 3f4bab6fa4f1..c95578a52218 100644 --- a/lib/matplotlib/tests/test_backend_qt5.py +++ b/lib/matplotlib/tests/test_backend_qt5.py @@ -145,6 +145,7 @@ def test_dpi_ratio_change(): qt_canvas.draw() qApp.processEvents() + qApp.processEvents() # The DPI and the renderer width/height change assert fig.dpi == 240 From ad1534dc15b66466fdbbb8e73230a0ecf115539c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 21 Sep 2017 21:46:16 -0400 Subject: [PATCH 3/5] MNT: ensure no draws are pending in paintEvent This prevents flickering during window resize. The issue is that the resize event triggers a paint and we have marked the figure as stale and requiring a re-draw. Sometimes the paint from the resize will process before the single-shot to trigger __draw_idle_agg and there will be a size-mismatch between the QImage and the buffer we are trying to fill it with which results in a visible flicker. --- lib/matplotlib/backends/backend_qt5agg.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index a4163fe3e1ae..b4c7104f0ef2 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -56,7 +56,10 @@ def paintEvent(self, e): In Qt, all drawing should be done inside of here when a widget is shown onscreen. """ - + # if there is a pending draw, run it now as we need the updated render + # to paint the widget + if self._agg_draw_pending: + self.__draw_idle_agg() # As described in __init__ above, we need to be careful in cases with # mixed resolution displays if dpi_ratio is changing between painting # events. @@ -137,6 +140,8 @@ def draw_idle(self): QtCore.QTimer.singleShot(0, self.__draw_idle_agg) def __draw_idle_agg(self, *args): + if not self._agg_draw_pending: + return if self.height() < 0 or self.width() < 0: self._agg_draw_pending = False return From a097c6f0de9caae97f173a2814ca1ffb813e4331 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 21 Sep 2017 21:52:45 -0400 Subject: [PATCH 4/5] STY: pep8 in docstrings --- lib/matplotlib/figure.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index d47fa4d2d935..8d4273cfe1b8 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -708,14 +708,14 @@ def set_size_inches(self, w, h=None, forward=True): Usage :: - fig.set_size_inches(w,h) # OR - fig.set_size_inches((w,h)) + fig.set_size_inches(w, h) # OR + fig.set_size_inches((w, h)) optional kwarg *forward=True* will cause the canvas size to be automatically updated; e.g., you can resize the figure window from the shell - ACCEPTS: a w,h tuple with w,h in inches + ACCEPTS: a w, h tuple with w, h in inches See Also -------- From 04ee64d9d1629c6348ad8a02e8314b8a026203e5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 25 Sep 2017 15:11:10 -0400 Subject: [PATCH 5/5] DOC: add comment to tests on why double processEvent --- lib/matplotlib/tests/test_backend_qt5.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_qt5.py b/lib/matplotlib/tests/test_backend_qt5.py index c95578a52218..8e903653fc25 100644 --- a/lib/matplotlib/tests/test_backend_qt5.py +++ b/lib/matplotlib/tests/test_backend_qt5.py @@ -145,6 +145,10 @@ def test_dpi_ratio_change(): 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