From 6a74cda44d8f467b4d5d2ac25e6ce7ac1efe3852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20W=C3=BCrtz?= Date: Wed, 19 Aug 2015 16:38:43 +0200 Subject: [PATCH 1/3] Qt: Redraw agg buffer in main thread. Implement FigureCanvasQTAggBase init. --- lib/matplotlib/backends/backend_qt4agg.py | 1 + lib/matplotlib/backends/backend_qt5.py | 18 ---------- lib/matplotlib/backends/backend_qt5agg.py | 43 ++++++++++++++++------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4agg.py b/lib/matplotlib/backends/backend_qt4agg.py index b6830573c983..392d5d5cd073 100644 --- a/lib/matplotlib/backends/backend_qt4agg.py +++ b/lib/matplotlib/backends/backend_qt4agg.py @@ -69,6 +69,7 @@ def __init__(self, figure): if DEBUG: print('FigureCanvasQtAgg: ', figure) FigureCanvasQT.__init__(self, figure) + FigureCanvasQTAggBase.__init__(self, figure) FigureCanvasAgg.__init__(self, figure) self._drawRect = None self.blitbox = None diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 33f085f61cf7..38940c844f81 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -239,7 +239,6 @@ def __init__(self, figure): super(FigureCanvasQT, self).__init__(figure=figure) self.figure = figure self.setMouseTracking(True) - self._idle = True w, h = self.get_width_height() self.resize(w, h) @@ -415,23 +414,6 @@ def stop_event_loop(self): stop_event_loop.__doc__ = FigureCanvasBase.stop_event_loop_default.__doc__ - def draw_idle(self): - # This cannot be a call to 'update', we need a slightly longer - # delay, otherwise mouse releases from zooming, panning, or - # lassoing might not finish processing and will not redraw properly. - # We use the guard flag to prevent infinite calls to 'draw_idle' which - # happens with the 'stale' figure & axes callbacks. - d = self._idle - self._idle = False - - def idle_draw(*args): - try: - self.draw() - finally: - self._idle = True - if d: - QtCore.QTimer.singleShot(0, idle_draw) - class MainWindow(QtWidgets.QMainWindow): closing = QtCore.Signal() diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index 5b8e1117cb3f..f5d105e8bdb6 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -58,8 +58,12 @@ class FigureCanvasQTAggBase(object): Public attribute - figure - A Figure instance - """ + figure - A Figure instance + """ + + def __init__(self, figure): + super(FigureCanvasQTAggBase, self).__init__(figure=figure) + self._agg_draw_pending = False def drawRectangle(self, rect): self._drawRect = rect @@ -71,10 +75,6 @@ def paintEvent(self, e): In Qt, all drawing should be done inside of here when a widget is shown onscreen. """ - # If we have not rendered the Agg backend yet, do so now. - if not hasattr(self, 'renderer'): - FigureCanvasAgg.draw(self) - # FigureCanvasQT.paintEvent(self, e) if DEBUG: print('FigureCanvasQtAgg.paintEvent: ', self, @@ -142,15 +142,33 @@ def paintEvent(self, e): def draw(self): """ - Draw the figure with Agg, and queue a request - for a Qt draw. + Draw the figure with Agg, and queue a request for a Qt draw. """ - # The Agg draw is done here; delaying it until the paintEvent - # causes problems with code that uses the result of the - # draw() to update plot elements. + # The Agg draw is done here; delaying causes problems with code that + # uses the result of the draw() to update plot elements. FigureCanvasAgg.draw(self) self.update() + def draw_idle(self): + """ + Queue redraw of the Agg buffer and request Qt paintEvent. + """ + # The Agg draw needs to be handled by the same thread matplotlib + # modifies the scene graph from. Post Agg draw request to the + # current event loop in order to ensure thread affinity and to + # accumulate multiple draw requests from event handling. + # TODO: queued signal connection might be safer than singleShot + if not self._agg_draw_pending: + self._agg_draw_pending = True + QtCore.QTimer.singleShot(0, self.__draw_idle_agg) + + def __draw_idle_agg(self, *args): + try: + FigureCanvasAgg.draw(self) + self.update() + finally: + self._agg_draw_pending = False + def blit(self, bbox=None): """ Blit the region in bbox @@ -186,8 +204,7 @@ class FigureCanvasQTAgg(FigureCanvasQTAggBase, def __init__(self, figure): if DEBUG: print('FigureCanvasQtAgg: ', figure) - FigureCanvasQT.__init__(self, figure) - FigureCanvasAgg.__init__(self, figure) + super(FigureCanvasQTAgg, self).__init__(figure=figure) self._drawRect = None self.blitbox = None self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) From f744e7c0caf5af3d4217341cdfa07f04322431f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20W=C3=BCrtz?= Date: Thu, 20 Aug 2015 10:32:20 +0200 Subject: [PATCH 2/3] Add handler for removing the rubberband when zoom is released --- lib/matplotlib/backend_bases.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index e2ffd2d13e24..d635e8b1c5dc 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2763,6 +2763,10 @@ def draw_rubberband(self, event, x0, y0, x1, y1): """Draw a rectangle rubberband to indicate zoom limits""" pass + def remove_rubberband(self): + """Remove the rubberband""" + pass + def forward(self, *args): """Move forward in the view lim stack""" self._views.forward() @@ -3033,6 +3037,8 @@ def release_zoom(self, event): self.canvas.mpl_disconnect(zoom_id) self._ids_zoom = [] + self.remove_rubberband() + if not self._xypress: return From d27ad9832fbf59613b6fae5787323c6caa49d5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20W=C3=BCrtz?= Date: Thu, 20 Aug 2015 10:33:52 +0200 Subject: [PATCH 3/3] Fix rubberband removal in Qt backend --- lib/matplotlib/backends/backend_qt5.py | 3 +++ lib/matplotlib/backends/backend_qt5agg.py | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 38940c844f81..5fa8353880c0 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -681,6 +681,9 @@ def draw_rubberband(self, event, x0, y0, x1, y1): rect = [int(val)for val in (min(x0, x1), min(y0, y1), w, h)] self.canvas.drawRectangle(rect) + def remove_rubberband(self): + self.canvas.drawRectangle(None) + def configure_subplots(self): image = os.path.join(matplotlib.rcParams['datapath'], 'images', 'matplotlib.png') diff --git a/lib/matplotlib/backends/backend_qt5agg.py b/lib/matplotlib/backends/backend_qt5agg.py index f5d105e8bdb6..7c110762eb9e 100644 --- a/lib/matplotlib/backends/backend_qt5agg.py +++ b/lib/matplotlib/backends/backend_qt5agg.py @@ -67,7 +67,7 @@ def __init__(self, figure): def drawRectangle(self, rect): self._drawRect = rect - self.draw_idle() + self.update() def paintEvent(self, e): """ @@ -136,9 +136,14 @@ def paintEvent(self, e): pixmap = QtGui.QPixmap.fromImage(qImage) p = QtGui.QPainter(self) p.drawPixmap(QtCore.QPoint(l, self.renderer.height-t), pixmap) + + # draw the zoom rectangle to the QPainter + if self._drawRect is not None: + p.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.DotLine)) + x, y, w, h = self._drawRect + p.drawRect(x, y, w, h) p.end() self.blitbox = None - self._drawRect = None def draw(self): """