From a9376df6c53ed58ff7c1250ec8c4db3108db9b8e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 18 Jun 2016 00:49:38 -0700 Subject: [PATCH 1/4] Switch the cursor to a busy cursor while redrawing. To test, plot e.g. `plot(rand(100000))` and zoom in manually (the redraw should take some time). The cursor should switch to a "busy" cursor (e.g. spinwheel). The switch doesn't seem to happen under the Tk and WX backends, probably due to some event loop intricacies I don't understand. --- lib/matplotlib/backend_tools.py | 2 +- lib/matplotlib/backends/backend_agg.py | 7 ++++++- lib/matplotlib/backends/backend_gtk.py | 1 + lib/matplotlib/backends/backend_gtk3.py | 1 + lib/matplotlib/backends/backend_qt5.py | 1 + lib/matplotlib/backends/backend_tkagg.py | 1 + lib/matplotlib/backends/backend_wx.py | 1 + 7 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 2fcc8a2bebfb..73f15fd42613 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -24,7 +24,7 @@ class Cursors(object): """Simple namespace for cursor reference""" - HAND, POINTER, SELECT_REGION, MOVE = list(range(4)) + HAND, POINTER, SELECT_REGION, MOVE, WAIT = list(range(5)) cursors = Cursors() # Views positions tool diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index b8b0eca92b0c..64149f51bd5a 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -30,7 +30,7 @@ from math import radians, cos, sin from matplotlib import verbose, rcParams, __version__ from matplotlib.backend_bases import ( - _Backend, FigureCanvasBase, FigureManagerBase, RendererBase) + _Backend, FigureCanvasBase, FigureManagerBase, RendererBase, cursors) from matplotlib.cbook import maxdict from matplotlib.figure import Figure from matplotlib.font_manager import findfont, get_font @@ -422,9 +422,14 @@ def draw(self): # acquire a lock on the shared font cache RendererAgg.lock.acquire() + toolbar = self.toolbar try: + if toolbar: + toolbar.set_cursor(cursors.WAIT) self.figure.draw(self.renderer) finally: + if toolbar: + toolbar.set_cursor(cursors.POINTER) RendererAgg.lock.release() def get_renderer(self, cleared=False): diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 757b2b7544c0..8f21833a8c80 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -54,6 +54,7 @@ cursors.HAND : gdk.Cursor(gdk.HAND2), cursors.POINTER : gdk.Cursor(gdk.LEFT_PTR), cursors.SELECT_REGION : gdk.Cursor(gdk.TCROSS), + cursors.WAIT : gdk.Cursor(gdk.WATCH), } # ref gtk+/gtk/gtkwidget.h diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a5f223a38753..a5b49ca3707b 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -51,6 +51,7 @@ cursors.HAND : Gdk.Cursor.new(Gdk.CursorType.HAND2), cursors.POINTER : Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR), cursors.SELECT_REGION : Gdk.Cursor.new(Gdk.CursorType.TCROSS), + cursors.WAIT : Gdk.Cursor.new(Gdk.CursorType.WATCH), } diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 9b924ce8ecb1..8407a6111d22 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -90,6 +90,7 @@ cursors.HAND: QtCore.Qt.PointingHandCursor, cursors.POINTER: QtCore.Qt.ArrowCursor, cursors.SELECT_REGION: QtCore.Qt.CrossCursor, + cursors.WAIT: QtCore.Qt.WaitCursor, } diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index f6190d4f369e..344faacc89a4 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -45,6 +45,7 @@ cursors.HAND: "hand2", cursors.POINTER: "arrow", cursors.SELECT_REGION: "tcross", + cursors.WAIT: "watch", } diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index f7df0707e637..a2b2e6614d17 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1474,6 +1474,7 @@ def updateButtonText(self, lst): cursors.HAND: wx.CURSOR_HAND, cursors.POINTER: wx.CURSOR_ARROW, cursors.SELECT_REGION: wx.CURSOR_CROSS, + cursors.WAIT: wx.CURSOR_WAIT, } From 42de0e883e6a2e85d1aaf3d0daa11e303c092551 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 19 Jun 2016 19:53:44 -0700 Subject: [PATCH 2/4] Also handle the Cairo backends. The wait cursor is not animated, though. --- lib/matplotlib/backends/backend_gtk.py | 6 +++++- lib/matplotlib/backends/backend_gtk3cairo.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 8f21833a8c80..2bd6ad660d88 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -387,16 +387,20 @@ def _render_figure(self, pixmap, width, height): def expose_event(self, widget, event): """Expose_event for all GTK backends. Should not be overridden. """ + toolbar = self.toolbar + if toolbar: + toolbar.set_cursor(cursors.WAIT) if GTK_WIDGET_DRAWABLE(self): if self._need_redraw: x, y, w, h = self.allocation self._pixmap_prepare (w, h) self._render_figure(self._pixmap, w, h) self._need_redraw = False - x, y, w, h = event.area self.window.draw_drawable (self.style.fg_gc[self.state], self._pixmap, x, y, x, y, w, h) + if toolbar: + toolbar.set_cursor(cursors.POINTER) return False # finish event propagation? filetypes = FigureCanvasBase.filetypes.copy() diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 958689d8e6ba..1687be749b7d 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -6,6 +6,7 @@ from . import backend_cairo, backend_gtk3 from .backend_cairo import cairo, HAS_CAIRO_CFFI from .backend_gtk3 import _BackendGTK3 +from matplotlib.backend_bases import cursors from matplotlib.figure import Figure @@ -35,10 +36,15 @@ def _render_figure(self, width, height): def on_draw_event(self, widget, ctx): """ GtkDrawable draw event, like expose_event in GTK 2.X """ + toolbar = self.toolbar + if toolbar: + toolbar.set_cursor(cursors.WAIT) self._renderer.set_context(ctx) allocation = self.get_allocation() x, y, w, h = allocation.x, allocation.y, allocation.width, allocation.height self._render_figure(w, h) + if toolbar: + toolbar.set_cursor(cursors.POINTER) return False # finish event propagation? From 54689d75f12b75ee728742ffc294ff006049cadd Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 31 May 2017 18:14:28 -0700 Subject: [PATCH 3/4] Trigger event loop after setting cursor. --- lib/matplotlib/backend_bases.py | 5 +++++ lib/matplotlib/backends/backend_gtk.py | 1 + lib/matplotlib/backends/backend_gtk3.py | 2 +- lib/matplotlib/backends/backend_tkagg.py | 1 + lib/matplotlib/backends/backend_wx.py | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 98aca9251d4c..424c84b3b7f0 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3201,6 +3201,11 @@ def save_figure(self, *args): def set_cursor(self, cursor): """Set the current cursor to one of the :class:`Cursors` enums values. + + If required by the backend, this method should trigger an update in + the backend event loop after the cursor is set, as this method may be + called e.g. before a long-running task during which the GUI is not + updated. """ def update(self): diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 2bd6ad660d88..0d207e702063 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -624,6 +624,7 @@ def set_message(self, s): def set_cursor(self, cursor): self.canvas.window.set_cursor(cursord[cursor]) + gtk.main_iteration() def release(self, event): try: del self._pixmapBack diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a5b49ca3707b..163224e98dd3 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -500,7 +500,7 @@ def set_message(self, s): def set_cursor(self, cursor): self.canvas.get_property("window").set_cursor(cursord[cursor]) - #self.canvas.set_cursor(cursord[cursor]) + Gtk.main_iteration() def release(self, event): try: del self._pixmapBack diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 344faacc89a4..781ef28d7c4a 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -698,6 +698,7 @@ def release(self, event): def set_cursor(self, cursor): self.window.configure(cursor=cursord[cursor]) + self.window.update_idletasks() def _Button(self, text, file, command, extension='.gif'): img_file = os.path.join( diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index a2b2e6614d17..ae99ffdbe3db 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1595,6 +1595,7 @@ def save_figure(self, *args): def set_cursor(self, cursor): cursor = wxc.Cursor(cursord[cursor]) self.canvas.SetCursor(cursor) + self.canvas.Update() def release(self, event): try: From d1516e6e22d78ee0f4d518dca6163b5bf9f752b1 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 3 Jun 2017 13:18:02 -0700 Subject: [PATCH 4/4] Fix cursor restoration. --- lib/matplotlib/backend_bases.py | 12 ++++++------ lib/matplotlib/backends/backend_agg.py | 2 +- lib/matplotlib/backends/backend_gtk.py | 2 +- lib/matplotlib/backends/backend_gtk3cairo.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 424c84b3b7f0..ea5ac3eee2cb 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2818,7 +2818,8 @@ def __init__(self, canvas): self._idPress = None self._idRelease = None self._active = None - self._lastCursor = None + # This cursor will be set after the initial draw. + self._lastCursor = cursors.POINTER self._init_toolbar() self._idDrag = self.canvas.mpl_connect( 'motion_notify_event', self.mouse_move) @@ -2904,14 +2905,13 @@ def _set_cursor(self, event): self.set_cursor(cursors.POINTER) self._lastCursor = cursors.POINTER else: - if self._active == 'ZOOM': - if self._lastCursor != cursors.SELECT_REGION: - self.set_cursor(cursors.SELECT_REGION) - self._lastCursor = cursors.SELECT_REGION + if (self._active == 'ZOOM' + and self._lastCursor != cursors.SELECT_REGION): + self.set_cursor(cursors.SELECT_REGION) + self._lastCursor = cursors.SELECT_REGION elif (self._active == 'PAN' and self._lastCursor != cursors.MOVE): self.set_cursor(cursors.MOVE) - self._lastCursor = cursors.MOVE def mouse_move(self, event): diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 64149f51bd5a..984638ab6657 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -429,7 +429,7 @@ def draw(self): self.figure.draw(self.renderer) finally: if toolbar: - toolbar.set_cursor(cursors.POINTER) + toolbar.set_cursor(toolbar._lastCursor) RendererAgg.lock.release() def get_renderer(self, cleared=False): diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 0d207e702063..01616f13c046 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -400,7 +400,7 @@ def expose_event(self, widget, event): self.window.draw_drawable (self.style.fg_gc[self.state], self._pixmap, x, y, x, y, w, h) if toolbar: - toolbar.set_cursor(cursors.POINTER) + toolbar.set_cursor(toolbar._lastCursor) return False # finish event propagation? filetypes = FigureCanvasBase.filetypes.copy() diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 1687be749b7d..79ba1fc2d24d 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -44,7 +44,7 @@ def on_draw_event(self, widget, ctx): x, y, w, h = allocation.x, allocation.y, allocation.width, allocation.height self._render_figure(w, h) if toolbar: - toolbar.set_cursor(cursors.POINTER) + toolbar.set_cursor(toolbar._lastCursor) return False # finish event propagation?