From 39c17b05f25e0fbf5245d3e36e96f57a8cf06360 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Sun, 26 Aug 2018 21:12:15 +0200 Subject: [PATCH 01/14] Don't use ClientDC any more but do all drawing in the EVT_PAINT handler. For rubberband selection etc., use Refresh to trigger a screen update. --- lib/matplotlib/backends/backend_wx.py | 271 ++++++--------------- lib/matplotlib/backends/backend_wxagg.py | 9 +- lib/matplotlib/backends/backend_wxcairo.py | 2 +- 3 files changed, 85 insertions(+), 197 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 7edf773c12a6..7bfe3f48a8de 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -531,6 +531,8 @@ def __init__(self, parent, id, figure): _log.debug("%s - __init__() - bitmap w:%d h:%d", type(self), w, h) # TODO: Add support for 'point' inspection and plot navigation. self._isDrawn = False + self._rubberband = None # a selection rectangle to be drawn + self._overlay = None self.Bind(wx.EVT_SIZE, self._onSize) self.Bind(wx.EVT_PAINT, self._onPaint) @@ -619,29 +621,6 @@ def _get_imagesave_wildcards(self): wildcards = '|'.join(wildcards) return wildcards, extensions, filter_index - def gui_repaint(self, drawDC=None, origin='WX'): - """ - Performs update of the displayed image on the GUI canvas, using the - supplied wx.PaintDC device context. - - The 'WXAgg' backend sets origin accordingly. - """ - _log.debug("%s - gui_repaint()", type(self)) - if self.IsShownOnScreen(): - if not drawDC: - # not called from OnPaint use a ClientDC - drawDC = wx.ClientDC(self) - - # following is for 'WX' backend on Windows - # the bitmap can not be in use by another DC, - # see GraphicsContextWx._cache - if wx.Platform == '__WXMSW__' and origin == 'WX': - img = self.bitmap.ConvertToImage() - bmp = img.ConvertToBitmap() - drawDC.DrawBitmap(bmp, 0, 0) - else: - drawDC.DrawBitmap(self.bitmap, 0, 0) - filetypes = { **FigureCanvasBase.filetypes, 'bmp': 'Windows bitmap', @@ -666,12 +645,41 @@ def print_figure(self, filename, *args, **kwargs): def _onPaint(self, event): """Called when wxPaintEvt is generated.""" _log.debug("%s - _onPaint()", type(self)) - drawDC = wx.PaintDC(self) if not self._isDrawn: - self.draw(drawDC=drawDC) + self.draw() + + # the bitmap can not be in use by another DC + img = self.bitmap.ConvertToImage() + bmp = img.ConvertToBitmap() + dc = wx.BufferedPaintDC(self, bmp) + + if not self._rubberband: + return + + # draw rubberband / selection using an overlay + if self._overlay is None: + self._overlay = wx.Overlay() + odc = wx.DCOverlay(self._overlay, dc) + + if not 'wxMac' in wx.PlatformInfo: + # draw a box with border and 50% transparency + dc = wx.GCDC(dc) + # Set the pen, for the box's border + bc = wx.BLUE + dc.SetPen(wx.Pen(colour=bc, width=1, style=wx.PENSTYLE_SOLID)) + # Create a brush (for the box's interior) with the same colour, + # but 50% transparency. + bc = wx.Colour(bc.red, bc.green, bc.blue, 0x80) + dc.SetBrush(wx.Brush(bc)) else: - self.gui_repaint(drawDC=drawDC) - drawDC.Destroy() + # draw without transparency which is buggy on Retina displays + dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + # Draw the rectangle + topLeft, bottomRight = self._rubberband + rect = wx.Rect(topLeft=topLeft, bottomRight=bottomRight) + dc.DrawRectangle(rect) def _onSize(self, event): """ @@ -824,11 +832,44 @@ def _onEnter(self, event): event.Skip() FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y)) + def _draw_rubberband(self, x0, y0, x1, y1): + # trigger a refresh to draw a rubberband-like selection box + previous_rubberband = self._rubberband + height = self.figure.bbox.height + y1 = height - y1 + y0 = height - y0 + self._rubberband = [(x0, y0), (x1, y1)] + self._refresh_rubberband(previous_rubberband) + + def _remove_rubberband(self): + # end drawing of a rubberband-like selection box + if not self._rubberband: + return + self._overlay.Reset() + self._overlay = None + self._refresh_rubberband() # trigger a later redraw over the last rubberband area + self._rubberband = None + + def _refresh_rubberband(self, previous_rubberband=None): + # initiate a refresh on the area that contains the previous and + # current selection / rubberband + points = list(self._rubberband) + if previous_rubberband: + points += previous_rubberband + # find the rectangle that covers the previous and current selection + top = min(point[1] for point in points) + left = min(point[0] for point in points) + bottom = max(point[1] for point in points) + right = max(point[0] for point in points) + + rect = wx.Rect(topLeft=wx.Point(left,top), bottomRight=wx.Point(right, bottom)) + self.Refresh(False, rect) + class FigureCanvasWx(_FigureCanvasWxBase): # Rendering to a Wx canvas using the deprecated Wx renderer. - def draw(self, drawDC=None): + def draw(self): """ Render the figure using RendererWx instance renderer, or using a previously defined renderer if none is specified. @@ -837,7 +878,6 @@ def draw(self, drawDC=None): self.renderer = RendererWx(self.bitmap, self.figure.dpi) self.figure.draw(self.renderer) self._isDrawn = True - self.gui_repaint(drawDC=drawDC) def print_bmp(self, filename, *args, **kwargs): return self._print_image(filename, wx.BITMAP_TYPE_BMP, *args, **kwargs) @@ -1120,11 +1160,6 @@ def __init__(self, canvas): self.canvas = canvas self._idle = True self.prevZoomRect = None - # for now, use alternate zoom-rectangle drawing on all - # Macs. N.B. In future versions of wx it may be possible to - # detect Retina displays with window.GetContentScaleFactor() - # and/or dc.GetContentScaleFactor() - self.retinaFix = 'wxMac' in wx.PlatformInfo def get_canvas(self, frame, fig): return type(self.canvas)(frame, -1, fig) @@ -1216,85 +1251,14 @@ def set_cursor(self, cursor): self.canvas.Update() def press(self, event): - if self._active == 'ZOOM': - if not self.retinaFix: - self.wxoverlay = wx.Overlay() - else: - if event.inaxes is not None: - self.savedRetinaImage = self.canvas.copy_from_bbox( - event.inaxes.bbox) - self.zoomStartX = event.xdata - self.zoomStartY = event.ydata - self.zoomAxes = event.inaxes + pass def release(self, event): if self._active == 'ZOOM': - # When the mouse is released we reset the overlay and it - # restores the former content to the window. - if not self.retinaFix: - self.wxoverlay.Reset() - del self.wxoverlay - else: - del self.savedRetinaImage - if self.prevZoomRect: - self.prevZoomRect.pop(0).remove() - self.prevZoomRect = None - if self.zoomAxes: - self.zoomAxes = None + self.canvas._remove_rubberband() def draw_rubberband(self, event, x0, y0, x1, y1): - if self.retinaFix: # On Macs, use the following code - # wx.DCOverlay does not work properly on Retina displays. - rubberBandColor = '#C0C0FF' - if self.prevZoomRect: - self.prevZoomRect.pop(0).remove() - self.canvas.restore_region(self.savedRetinaImage) - X0, X1 = self.zoomStartX, event.xdata - Y0, Y1 = self.zoomStartY, event.ydata - lineX = (X0, X0, X1, X1, X0) - lineY = (Y0, Y1, Y1, Y0, Y0) - self.prevZoomRect = self.zoomAxes.plot( - lineX, lineY, '-', color=rubberBandColor) - self.zoomAxes.draw_artist(self.prevZoomRect[0]) - self.canvas.blit(self.zoomAxes.bbox) - return - - # Use an Overlay to draw a rubberband-like bounding box. - - dc = wx.ClientDC(self.canvas) - odc = wx.DCOverlay(self.wxoverlay, dc) - odc.Clear() - - # Mac's DC is already the same as a GCDC, and it causes - # problems with the overlay if we try to use an actual - # wx.GCDC so don't try it. - if 'wxMac' not in wx.PlatformInfo: - dc = wx.GCDC(dc) - - height = self.canvas.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - - if y1 < y0: - y0, y1 = y1, y0 - if x1 < x0: - x0, x1 = x1, x0 - - w = x1 - x0 - h = y1 - y0 - rect = wx.Rect(x0, y0, w, h) - - rubberBandColor = '#C0C0FF' # or load from config? - - # Set a pen for the border - color = wx.Colour(rubberBandColor) - dc.SetPen(wx.Pen(color, 1)) - - # use the same color, plus alpha for the brush - r, g, b, a = color.Get(True) - color.Set(r, g, b, 0x60) - dc.SetBrush(wx.Brush(color)) - dc.DrawRectangle(rect) + self.canvas._draw_rubberband(x0, y0, x1, y1) @cbook.deprecated("3.2") def set_status_bar(self, statbar): @@ -1451,91 +1415,16 @@ def set_cursor(self, cursor): self._make_classic_style_pseudo_toolbar(), cursor) -if 'wxMac' not in wx.PlatformInfo: - # on most platforms, use overlay - class RubberbandWx(backend_tools.RubberbandBase): - def __init__(self, *args, **kwargs): - backend_tools.RubberbandBase.__init__(self, *args, **kwargs) - self.wxoverlay = None - - def draw_rubberband(self, x0, y0, x1, y1): - # Use an Overlay to draw a rubberband-like bounding box. - if self.wxoverlay is None: - self.wxoverlay = wx.Overlay() - dc = wx.ClientDC(self.canvas) - odc = wx.DCOverlay(self.wxoverlay, dc) - odc.Clear() - - dc = wx.GCDC(dc) - - height = self.canvas.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - - if y1 < y0: - y0, y1 = y1, y0 - if x1 < x0: - x0, x1 = x1, x0 - - w = x1 - x0 - h = y1 - y0 - rect = wx.Rect(x0, y0, w, h) - - rubberBandColor = '#C0C0FF' # or load from config? - - # Set a pen for the border - color = wx.Colour(rubberBandColor) - dc.SetPen(wx.Pen(color, 1)) - - # use the same color, plus alpha for the brush - r, g, b, a = color.Get(True) - color.Set(r, g, b, 0x60) - dc.SetBrush(wx.Brush(color)) - dc.DrawRectangle(rect) +class RubberbandWx(backend_tools.RubberbandBase): + def __init__(self, *args, **kwargs): + backend_tools.RubberbandBase.__init__(self, *args, **kwargs) - def remove_rubberband(self): - if self.wxoverlay is None: - return - self.wxoverlay.Reset() - self.wxoverlay = None - -else: - # on Mac OS retina displays DCOverlay does not work - # and dc.SetLogicalFunction does not have an effect on any display - # the workaround is to blit the full image for remove_rubberband - class RubberbandWx(backend_tools.RubberbandBase): - def __init__(self, *args, **kwargs): - backend_tools.RubberbandBase.__init__(self, *args, **kwargs) - self._rect = None - - def draw_rubberband(self, x0, y0, x1, y1): - dc = wx.ClientDC(self.canvas) - # this would be required if the Canvas is a ScrolledWindow, - # which is not the case for now - # self.PrepareDC(dc) - - # delete old rubberband - if self._rect: - self.remove_rubberband(dc) - - # draw new rubberband - dc.SetPen(wx.Pen(wx.BLACK, 1, wx.SOLID)) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - self._rect = (x0, self.canvas._height-y0, x1-x0, -y1+y0) - dc.DrawRectangle(self._rect) + def draw_rubberband(self, x0, y0, x1, y1): + # Use an Overlay to draw a rubberband-like bounding box. + self.canvas._draw_rubberband(x0, y0, x1, y1) - def remove_rubberband(self, dc=None): - if not self._rect: - return - if self.canvas.bitmap: - if dc is None: - dc = wx.ClientDC(self.canvas) - dc.DrawBitmap(self.canvas.bitmap, 0, 0) - # for testing the method on Windows, use this code instead: - # img = self.canvas.bitmap.ConvertToImage() - # bmp = img.ConvertToBitmap() - # dc.DrawBitmap(bmp, 0, 0) - self._rect = None + def remove_rubberband(self): + self.canvas._remove_rubberband() class _HelpDialog(wx.Dialog): diff --git a/lib/matplotlib/backends/backend_wxagg.py b/lib/matplotlib/backends/backend_wxagg.py index 3bbb4ab25f4a..a91da955b267 100644 --- a/lib/matplotlib/backends/backend_wxagg.py +++ b/lib/matplotlib/backends/backend_wxagg.py @@ -22,7 +22,7 @@ class FigureCanvasWxAgg(FigureCanvasAgg, _FigureCanvasWxBase): size. """ - def draw(self, drawDC=None): + def draw(self): """ Render the figure using agg. """ @@ -30,13 +30,11 @@ def draw(self, drawDC=None): self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) self._isDrawn = True - self.gui_repaint(drawDC=drawDC, origin='WXAgg') def blit(self, bbox=None): - # docstring inherited if bbox is None: self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) - self.gui_repaint() + self.Refresh() return l, b, w, h = bbox.bounds @@ -56,7 +54,8 @@ def blit(self, bbox=None): destDC.SelectObject(wx.NullBitmap) srcDC.SelectObject(wx.NullBitmap) - self.gui_repaint() + + self.Refresh(rect=wx.Rect(x,y, int(w), int(h))) def _convert_agg_to_wx_bitmap(agg, bbox): diff --git a/lib/matplotlib/backends/backend_wxcairo.py b/lib/matplotlib/backends/backend_wxcairo.py index 5fcd263685a2..c7b0b48a45d0 100644 --- a/lib/matplotlib/backends/backend_wxcairo.py +++ b/lib/matplotlib/backends/backend_wxcairo.py @@ -38,7 +38,7 @@ def draw(self, drawDC=None): self.figure.draw(self._renderer) self.bitmap = wxcairo.BitmapFromImageSurface(surface) self._isDrawn = True - self.gui_repaint(drawDC=drawDC, origin='WXCairo') + self.Refresh() @_BackendWx.export From cff5f637af9d5c7e19be725cc731a7d442c141d3 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Sun, 26 Aug 2018 23:01:33 +0200 Subject: [PATCH 02/14] rubberband: extend by one pixel to avoid residuals on Mac OS --- lib/matplotlib/backends/backend_wx.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 7bfe3f48a8de..4c6dec0c1fca 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -861,6 +861,16 @@ def _refresh_rubberband(self, previous_rubberband=None): left = min(point[0] for point in points) bottom = max(point[1] for point in points) right = max(point[0] for point in points) + if previous_rubberband: + # extend by one pixel to avoid residuals on Mac OS + if left > 0: + left -= 1 + if top > 0: + top -= 1 + if bottom < self.figure.bbox.height - 1: + bottom += 1 + if top < self.figure.bbox.width - 1: + top += 1 rect = wx.Rect(topLeft=wx.Point(left,top), bottomRight=wx.Point(right, bottom)) self.Refresh(False, rect) From b31c7366f41888525b4b22f1baf19c82269f4913 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Sun, 26 Aug 2018 23:11:51 +0200 Subject: [PATCH 03/14] fix bug in previous commit --- lib/matplotlib/backends/backend_wx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 4c6dec0c1fca..b1befe1d0cc1 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -861,6 +861,7 @@ def _refresh_rubberband(self, previous_rubberband=None): left = min(point[0] for point in points) bottom = max(point[1] for point in points) right = max(point[0] for point in points) + if previous_rubberband: # extend by one pixel to avoid residuals on Mac OS if left > 0: @@ -869,8 +870,8 @@ def _refresh_rubberband(self, previous_rubberband=None): top -= 1 if bottom < self.figure.bbox.height - 1: bottom += 1 - if top < self.figure.bbox.width - 1: - top += 1 + if right < self.figure.bbox.width - 1: + right += 1 rect = wx.Rect(topLeft=wx.Point(left,top), bottomRight=wx.Point(right, bottom)) self.Refresh(False, rect) From 452ab5c7a293da17bbf773927ab5e69afd2b7035 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Sun, 26 Aug 2018 23:15:06 +0200 Subject: [PATCH 04/14] shorten lines for PEP 8 --- lib/matplotlib/backends/backend_wx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index b1befe1d0cc1..5e732bc73609 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -847,7 +847,7 @@ def _remove_rubberband(self): return self._overlay.Reset() self._overlay = None - self._refresh_rubberband() # trigger a later redraw over the last rubberband area + self._refresh_rubberband() # trigger a later redraw self._rubberband = None def _refresh_rubberband(self, previous_rubberband=None): @@ -873,7 +873,8 @@ def _refresh_rubberband(self, previous_rubberband=None): if right < self.figure.bbox.width - 1: right += 1 - rect = wx.Rect(topLeft=wx.Point(left,top), bottomRight=wx.Point(right, bottom)) + rect = wx.Rect(topLeft=wx.Point(left,top), + bottomRight=wx.Point(right, bottom)) self.Refresh(False, rect) From 779efc017a03951091daf18ad55d139e29a3a0d6 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Sun, 26 Aug 2018 23:42:39 +0200 Subject: [PATCH 05/14] trigger Refresh after draw --- lib/matplotlib/backends/backend_wx.py | 1 + lib/matplotlib/backends/backend_wxagg.py | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 5e732bc73609..5db610ba7f79 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -890,6 +890,7 @@ def draw(self): self.renderer = RendererWx(self.bitmap, self.figure.dpi) self.figure.draw(self.renderer) self._isDrawn = True + self.Refresh() def print_bmp(self, filename, *args, **kwargs): return self._print_image(filename, wx.BITMAP_TYPE_BMP, *args, **kwargs) diff --git a/lib/matplotlib/backends/backend_wxagg.py b/lib/matplotlib/backends/backend_wxagg.py index a91da955b267..203901819ba0 100644 --- a/lib/matplotlib/backends/backend_wxagg.py +++ b/lib/matplotlib/backends/backend_wxagg.py @@ -30,6 +30,7 @@ def draw(self): self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) self._isDrawn = True + self.Refresh() def blit(self, bbox=None): if bbox is None: From 4ff217daba48f5f7ce920afc9ee206196ddf0463 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 27 Aug 2018 00:07:38 +0200 Subject: [PATCH 06/14] PEP 8 compliance --- lib/matplotlib/backends/backend_wx.py | 10 +++++----- lib/matplotlib/backends/backend_wxagg.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 5db610ba7f79..adbfc575e261 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -661,7 +661,7 @@ def _onPaint(self, event): self._overlay = wx.Overlay() odc = wx.DCOverlay(self._overlay, dc) - if not 'wxMac' in wx.PlatformInfo: + if 'wxMac' not in wx.PlatformInfo: # draw a box with border and 50% transparency dc = wx.GCDC(dc) # Set the pen, for the box's border @@ -857,10 +857,10 @@ def _refresh_rubberband(self, previous_rubberband=None): if previous_rubberband: points += previous_rubberband # find the rectangle that covers the previous and current selection - top = min(point[1] for point in points) - left = min(point[0] for point in points) + top = min(point[1] for point in points) + left = min(point[0] for point in points) bottom = max(point[1] for point in points) - right = max(point[0] for point in points) + right = max(point[0] for point in points) if previous_rubberband: # extend by one pixel to avoid residuals on Mac OS @@ -873,7 +873,7 @@ def _refresh_rubberband(self, previous_rubberband=None): if right < self.figure.bbox.width - 1: right += 1 - rect = wx.Rect(topLeft=wx.Point(left,top), + rect = wx.Rect(topLeft=wx.Point(left, top), bottomRight=wx.Point(right, bottom)) self.Refresh(False, rect) diff --git a/lib/matplotlib/backends/backend_wxagg.py b/lib/matplotlib/backends/backend_wxagg.py index 203901819ba0..60dc66e08a21 100644 --- a/lib/matplotlib/backends/backend_wxagg.py +++ b/lib/matplotlib/backends/backend_wxagg.py @@ -56,7 +56,7 @@ def blit(self, bbox=None): destDC.SelectObject(wx.NullBitmap) srcDC.SelectObject(wx.NullBitmap) - self.Refresh(rect=wx.Rect(x,y, int(w), int(h))) + self.Refresh(rect=wx.Rect(x, y, int(w), int(h))) def _convert_agg_to_wx_bitmap(agg, bbox): From 97fccf1ee1ccad22ffb296adc216569781ca4612 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 27 Aug 2018 23:12:24 +0200 Subject: [PATCH 07/14] Use dashed black line for rubberband on Mac OS --- lib/matplotlib/backends/backend_wx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index adbfc575e261..6700733c159d 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -673,7 +673,7 @@ def _onPaint(self, event): dc.SetBrush(wx.Brush(bc)) else: # draw without transparency which is buggy on Retina displays - dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID)) + dc.SetPen(wx.Pen(wx.BLACK, 1, wx.PENSTYLE_SHORT_DASH)) dc.SetBrush(wx.TRANSPARENT_BRUSH) # Draw the rectangle From 5f408d710dd1dafa8991bc5d4d86247dc176625b Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 27 Aug 2018 23:12:49 +0200 Subject: [PATCH 08/14] clearer calculation of extended rubberband box --- lib/matplotlib/backends/backend_wx.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 6700733c159d..255fcf79d858 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -864,14 +864,10 @@ def _refresh_rubberband(self, previous_rubberband=None): if previous_rubberband: # extend by one pixel to avoid residuals on Mac OS - if left > 0: - left -= 1 - if top > 0: - top -= 1 - if bottom < self.figure.bbox.height - 1: - bottom += 1 - if right < self.figure.bbox.width - 1: - right += 1 + left = max(left - 1, 0) + top = max(top - 1, 0) + bottom = min(bottom + 1, self.figure.bbox.height - 1) + right = min(right + 1, self.figure.bbox.width - 1) rect = wx.Rect(topLeft=wx.Point(left, top), bottomRight=wx.Point(right, bottom)) From 98146f3030e7e60ec97821c636365e0e7019142a Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 27 Aug 2018 23:56:26 +0200 Subject: [PATCH 09/14] separate rendering and Refresh into _draw() and draw() --- lib/matplotlib/backends/backend_wx.py | 104 +++++++++++++++++++-- lib/matplotlib/backends/backend_wxagg.py | 8 +- lib/matplotlib/backends/backend_wxcairo.py | 8 +- 3 files changed, 112 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 255fcf79d858..bd85cb58c550 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -646,7 +646,7 @@ def _onPaint(self, event): """Called when wxPaintEvt is generated.""" _log.debug("%s - _onPaint()", type(self)) if not self._isDrawn: - self.draw() + self._draw() # the bitmap can not be in use by another DC img = self.bitmap.ConvertToImage() @@ -877,15 +877,18 @@ def _refresh_rubberband(self, previous_rubberband=None): class FigureCanvasWx(_FigureCanvasWxBase): # Rendering to a Wx canvas using the deprecated Wx renderer. + def _draw(self): + _log.debug("%s - _draw()", type(self)) + self.renderer = RendererWx(self.bitmap, self.figure.dpi) + self.figure.draw(self.renderer) + self._isDrawn = True + def draw(self): """ Render the figure using RendererWx instance renderer, or using a previously defined renderer if none is specified. """ - _log.debug("%s - draw()", type(self)) - self.renderer = RendererWx(self.bitmap, self.figure.dpi) - self.figure.draw(self.renderer) - self._isDrawn = True + self._draw() self.Refresh() def print_bmp(self, filename, *args, **kwargs): @@ -957,7 +960,7 @@ def _print_image(self, filename, filetype, *args, **kwargs): # been cleaned up. The artist contains() methods will fail # otherwise. if self._isDrawn: - self.draw() + self._draw() self.Refresh() @@ -1507,6 +1510,95 @@ def trigger(self, *args, **kwargs): backend_tools.ToolCopyToClipboard = ToolCopyToClipboardWx +<<<<<<< HEAD +======= +# < Additions for printing support: Matt Newville + +class PrintoutWx(wx.Printout): + """ + Simple wrapper around wx Printout class -- all the real work + here is scaling the matplotlib canvas bitmap to the current + printer's definition. + """ + + def __init__(self, canvas, width=5.5, margin=0.5, title='matplotlib'): + wx.Printout.__init__(self, title=title) + self.canvas = canvas + # width, in inches of output figure (approximate) + self.width = width + self.margin = margin + + def HasPage(self, page): + # current only supports 1 page print + return page == 1 + + def GetPageInfo(self): + return (1, 1, 1, 1) + + def OnPrintPage(self, page): + self.canvas._draw() + + dc = self.GetDC() + ppw, pph = self.GetPPIPrinter() # printer's pixels per in + pgw, pgh = self.GetPageSizePixels() # page size in pixels + dcw, dch = dc.GetSize() + grw, grh = self.canvas.GetSize() + + # save current figure dpi resolution and bg color, + # so that we can temporarily set them to the dpi of + # the printer, and the bg color to white + bgcolor = self.canvas.figure.get_facecolor() + fig_dpi = self.canvas.figure.dpi + + # draw the bitmap, scaled appropriately + vscale = float(ppw) / fig_dpi + + # set figure resolution,bg color for printer + self.canvas.figure.dpi = ppw + self.canvas.figure.set_facecolor('#FFFFFF') + + renderer = RendererWx(self.canvas.bitmap, self.canvas.figure.dpi) + self.canvas.figure.draw(renderer) + self.canvas.bitmap.SetWidth( + int(self.canvas.bitmap.GetWidth() * vscale)) + self.canvas.bitmap.SetHeight( + int(self.canvas.bitmap.GetHeight() * vscale)) + self.canvas._draw() + + # page may need additional scaling on preview + page_scale = 1.0 + if self.IsPreview(): + page_scale = float(dcw) / pgw + + # get margin in pixels = (margin in in) * (pixels/in) + top_margin = int(self.margin * pph * page_scale) + left_margin = int(self.margin * ppw * page_scale) + + # set scale so that width of output is self.width inches + # (assuming grw is size of graph in inches....) + user_scale = (self.width * fig_dpi * page_scale) / float(grw) + + dc.SetDeviceOrigin(left_margin, top_margin) + dc.SetUserScale(user_scale, user_scale) + + # this cute little number avoid API inconsistencies in wx + try: + dc.DrawBitmap(self.canvas.bitmap, 0, 0) + except Exception: + try: + dc.DrawBitmap(self.canvas.bitmap, (0, 0)) + except Exception: + pass + + # restore original figure resolution + self.canvas.figure.set_facecolor(bgcolor) + self.canvas.figure.dpi = fig_dpi + self.canvas.draw() + return True +# > + + +>>>>>>> d2cb5900b... separate rendering and Refresh into _draw() and draw() @_Backend.export class _BackendWx(_Backend): FigureCanvas = FigureCanvasWx diff --git a/lib/matplotlib/backends/backend_wxagg.py b/lib/matplotlib/backends/backend_wxagg.py index 60dc66e08a21..bdf6e017e9ed 100644 --- a/lib/matplotlib/backends/backend_wxagg.py +++ b/lib/matplotlib/backends/backend_wxagg.py @@ -22,7 +22,7 @@ class FigureCanvasWxAgg(FigureCanvasAgg, _FigureCanvasWxBase): size. """ - def draw(self): + def _draw(self): """ Render the figure using agg. """ @@ -30,6 +30,12 @@ def draw(self): self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) self._isDrawn = True + + def draw(self): + """ + Render the figure using agg and trigger a screen refresh. + """ + self._draw() self.Refresh() def blit(self, bbox=None): diff --git a/lib/matplotlib/backends/backend_wxcairo.py b/lib/matplotlib/backends/backend_wxcairo.py index c7b0b48a45d0..77a37b97df84 100644 --- a/lib/matplotlib/backends/backend_wxcairo.py +++ b/lib/matplotlib/backends/backend_wxcairo.py @@ -29,7 +29,7 @@ def __init__(self, parent, id, figure): FigureCanvasCairo.__init__(self, figure) self._renderer = RendererCairo(self.figure.dpi) - def draw(self, drawDC=None): + def _draw(self): width = int(self.figure.bbox.width) height = int(self.figure.bbox.height) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) @@ -38,6 +38,12 @@ def draw(self, drawDC=None): self.figure.draw(self._renderer) self.bitmap = wxcairo.BitmapFromImageSurface(surface) self._isDrawn = True + + def draw(self): + """ + Render the figure and trigger a screen refresh. + """ + self._draw() self.Refresh() From b86d842b1a01c9ad1e747386e14ce97436d837a0 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 3 Sep 2018 22:07:33 +0200 Subject: [PATCH 10/14] re-introduce attribute _retinaFix, but on canvas level --- lib/matplotlib/backends/backend_wx.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index bd85cb58c550..16afc9ae11e9 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -507,6 +507,7 @@ class _FigureCanvasWxBase(FigureCanvasBase, wx.Panel): wx.WXK_NUMPAD_INSERT: 'insert', wx.WXK_NUMPAD_DELETE: 'delete', } + _retinaFix = 'wxMac' in wx.PlatformInfo def __init__(self, parent, id, figure): """ @@ -661,7 +662,7 @@ def _onPaint(self, event): self._overlay = wx.Overlay() odc = wx.DCOverlay(self._overlay, dc) - if 'wxMac' not in wx.PlatformInfo: + if not self._retinaFix: # draw a box with border and 50% transparency dc = wx.GCDC(dc) # Set the pen, for the box's border From ac89e8b0cb58a3a83da4eb8272a43441edb213c7 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 3 Sep 2018 22:09:02 +0200 Subject: [PATCH 11/14] add API change notes --- .../2018-09-01-DS-backends_wx.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 doc/api/next_api_changes/2018-09-01-DS-backends_wx.rst diff --git a/doc/api/next_api_changes/2018-09-01-DS-backends_wx.rst b/doc/api/next_api_changes/2018-09-01-DS-backends_wx.rst new file mode 100644 index 000000000000..645a052cd8ec --- /dev/null +++ b/doc/api/next_api_changes/2018-09-01-DS-backends_wx.rst @@ -0,0 +1,15 @@ +wx Backends +----------- + +The internal implementation of the wx backends was changed to do all +the screen painting inside the ``_OnPaint`` method which handles wx +``EVT_PAINT`` events. +So for a screen update due to a call to ``draw`` or for drawing a +selection rubberband, the ``Refresh`` method is called to trigger +a later paint event instead of drawing directly to a ``ClientDC``. + +The atribute ``_retinaFix`` has moved from ``NavigationToolbar2Wx`` +to ``_FigureCanvasWxBase``. + +The method ``gui_repaint`` of all wx canvases has been removed. +The ``draw`` method no longer accepts an argument ``drawDC``. From 2f00cdcf32d19c6e069f1eb94b216c78f434a2a6 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Fri, 26 Oct 2018 22:00:16 +0200 Subject: [PATCH 12/14] remove unused overlay; unify code for all platforms --- lib/matplotlib/backends/backend_wx.py | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 16afc9ae11e9..df62e9dadabf 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -657,25 +657,16 @@ def _onPaint(self, event): if not self._rubberband: return - # draw rubberband / selection using an overlay - if self._overlay is None: - self._overlay = wx.Overlay() - odc = wx.DCOverlay(self._overlay, dc) - + # draw rubberband / selection: a box with border and 50% transparency if not self._retinaFix: - # draw a box with border and 50% transparency dc = wx.GCDC(dc) - # Set the pen, for the box's border - bc = wx.BLUE - dc.SetPen(wx.Pen(colour=bc, width=1, style=wx.PENSTYLE_SOLID)) - # Create a brush (for the box's interior) with the same colour, - # but 50% transparency. - bc = wx.Colour(bc.red, bc.green, bc.blue, 0x80) - dc.SetBrush(wx.Brush(bc)) - else: - # draw without transparency which is buggy on Retina displays - dc.SetPen(wx.Pen(wx.BLACK, 1, wx.PENSTYLE_SHORT_DASH)) - dc.SetBrush(wx.TRANSPARENT_BRUSH) + # Set the pen, for the box's border + bc = wx.BLUE + dc.SetPen(wx.Pen(colour=bc, width=1, style=wx.PENSTYLE_SOLID)) + # Create a brush (for the box's interior) with the same colour, + # but 50% transparency. + bc = wx.Colour(bc.red, bc.green, bc.blue, 0x80) + dc.SetBrush(wx.Brush(bc)) # Draw the rectangle topLeft, bottomRight = self._rubberband @@ -846,8 +837,6 @@ def _remove_rubberband(self): # end drawing of a rubberband-like selection box if not self._rubberband: return - self._overlay.Reset() - self._overlay = None self._refresh_rubberband() # trigger a later redraw self._rubberband = None From 885c7d5278d61125245935537fd8816ba31b5ddb Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 29 Oct 2018 23:19:34 +0100 Subject: [PATCH 13/14] use x,y,width,height for rubberband rectangle --- lib/matplotlib/backends/backend_wx.py | 43 ++++++++++++--------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index df62e9dadabf..88550a714f04 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -669,9 +669,7 @@ def _onPaint(self, event): dc.SetBrush(wx.Brush(bc)) # Draw the rectangle - topLeft, bottomRight = self._rubberband - rect = wx.Rect(topLeft=topLeft, bottomRight=bottomRight) - dc.DrawRectangle(rect) + dc.DrawRectangle(*self._rubberband) def _onSize(self, event): """ @@ -826,41 +824,38 @@ def _onEnter(self, event): def _draw_rubberband(self, x0, y0, x1, y1): # trigger a refresh to draw a rubberband-like selection box + width = abs(x1 - x0) + x0 = min(x0, x1) + height = abs(y1 - y0) + y0 = self.figure.bbox.height - max(y0, y1) previous_rubberband = self._rubberband - height = self.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - self._rubberband = [(x0, y0), (x1, y1)] + self._rubberband = (x0, y0, width, height) self._refresh_rubberband(previous_rubberband) def _remove_rubberband(self): # end drawing of a rubberband-like selection box if not self._rubberband: return - self._refresh_rubberband() # trigger a later redraw + previous_rubberband = self._rubberband self._rubberband = None + self._refresh_rubberband(previous_rubberband) # trigger a later redraw def _refresh_rubberband(self, previous_rubberband=None): # initiate a refresh on the area that contains the previous and # current selection / rubberband - points = list(self._rubberband) - if previous_rubberband: - points += previous_rubberband - # find the rectangle that covers the previous and current selection - top = min(point[1] for point in points) - left = min(point[0] for point in points) - bottom = max(point[1] for point in points) - right = max(point[0] for point in points) - + if self._rubberband: + rect = wx.Rect(self._rubberband) + else: + rect = None if previous_rubberband: + previous_rubberband = wx.Rect(*previous_rubberband) # extend by one pixel to avoid residuals on Mac OS - left = max(left - 1, 0) - top = max(top - 1, 0) - bottom = min(bottom + 1, self.figure.bbox.height - 1) - right = min(right + 1, self.figure.bbox.width - 1) - - rect = wx.Rect(topLeft=wx.Point(left, top), - bottomRight=wx.Point(right, bottom)) + previous_rubberband.Inflate(1, 1) + if rect: + rect.Union(previous_rubberband) + else: + rect = previous_rubberband + rect.Intersect(self.Rect) self.Refresh(False, rect) From 24bfbed1aa698325019960fe10afcee51be80914 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Sat, 28 Mar 2020 19:34:44 +0100 Subject: [PATCH 14/14] forgotten conflict from rebasing --- lib/matplotlib/backends/backend_wx.py | 89 --------------------------- 1 file changed, 89 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 88550a714f04..c2cdad39086a 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1495,95 +1495,6 @@ def trigger(self, *args, **kwargs): backend_tools.ToolCopyToClipboard = ToolCopyToClipboardWx -<<<<<<< HEAD -======= -# < Additions for printing support: Matt Newville - -class PrintoutWx(wx.Printout): - """ - Simple wrapper around wx Printout class -- all the real work - here is scaling the matplotlib canvas bitmap to the current - printer's definition. - """ - - def __init__(self, canvas, width=5.5, margin=0.5, title='matplotlib'): - wx.Printout.__init__(self, title=title) - self.canvas = canvas - # width, in inches of output figure (approximate) - self.width = width - self.margin = margin - - def HasPage(self, page): - # current only supports 1 page print - return page == 1 - - def GetPageInfo(self): - return (1, 1, 1, 1) - - def OnPrintPage(self, page): - self.canvas._draw() - - dc = self.GetDC() - ppw, pph = self.GetPPIPrinter() # printer's pixels per in - pgw, pgh = self.GetPageSizePixels() # page size in pixels - dcw, dch = dc.GetSize() - grw, grh = self.canvas.GetSize() - - # save current figure dpi resolution and bg color, - # so that we can temporarily set them to the dpi of - # the printer, and the bg color to white - bgcolor = self.canvas.figure.get_facecolor() - fig_dpi = self.canvas.figure.dpi - - # draw the bitmap, scaled appropriately - vscale = float(ppw) / fig_dpi - - # set figure resolution,bg color for printer - self.canvas.figure.dpi = ppw - self.canvas.figure.set_facecolor('#FFFFFF') - - renderer = RendererWx(self.canvas.bitmap, self.canvas.figure.dpi) - self.canvas.figure.draw(renderer) - self.canvas.bitmap.SetWidth( - int(self.canvas.bitmap.GetWidth() * vscale)) - self.canvas.bitmap.SetHeight( - int(self.canvas.bitmap.GetHeight() * vscale)) - self.canvas._draw() - - # page may need additional scaling on preview - page_scale = 1.0 - if self.IsPreview(): - page_scale = float(dcw) / pgw - - # get margin in pixels = (margin in in) * (pixels/in) - top_margin = int(self.margin * pph * page_scale) - left_margin = int(self.margin * ppw * page_scale) - - # set scale so that width of output is self.width inches - # (assuming grw is size of graph in inches....) - user_scale = (self.width * fig_dpi * page_scale) / float(grw) - - dc.SetDeviceOrigin(left_margin, top_margin) - dc.SetUserScale(user_scale, user_scale) - - # this cute little number avoid API inconsistencies in wx - try: - dc.DrawBitmap(self.canvas.bitmap, 0, 0) - except Exception: - try: - dc.DrawBitmap(self.canvas.bitmap, (0, 0)) - except Exception: - pass - - # restore original figure resolution - self.canvas.figure.set_facecolor(bgcolor) - self.canvas.figure.dpi = fig_dpi - self.canvas.draw() - return True -# > - - ->>>>>>> d2cb5900b... separate rendering and Refresh into _draw() and draw() @_Backend.export class _BackendWx(_Backend): FigureCanvas = FigureCanvasWx