From 8a8786fe7a7226a49cd4af68a615ac470417cf0c Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Sun, 28 Jun 2020 13:18:56 -0400 Subject: [PATCH 1/9] Library code should never call update_idletasks --- lib/matplotlib/backends/_backend_tk.py | 3 --- lib/matplotlib/backends/backend_tkagg.py | 2 -- lib/matplotlib/backends/backend_tkcairo.py | 1 - 3 files changed, 6 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index f8516584187c..6ac3ef32afba 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -209,7 +209,6 @@ def __init__(self, figure, master=None, resize_callback=None): # to the window and filter. def filter_destroy(event): if event.widget is self._tkcanvas: - self._master.update_idletasks() self.close_event() root.bind("", filter_destroy, "+") @@ -554,8 +553,6 @@ def set_cursor(self, cursor): window.configure(cursor=cursord[cursor]) except tkinter.TclError: pass - else: - window.update_idletasks() def _Button(self, text, image_file, toggle, command): image = (tk.PhotoImage(master=self, file=image_file) diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 1cb38b7559ed..484d5a40edbe 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -8,12 +8,10 @@ class FigureCanvasTkAgg(FigureCanvasAgg, FigureCanvasTk): def draw(self): super().draw() _backend_tk.blit(self._tkphoto, self.renderer._renderer, (0, 1, 2, 3)) - self._master.update_idletasks() def blit(self, bbox=None): _backend_tk.blit( self._tkphoto, self.renderer._renderer, (0, 1, 2, 3), bbox=bbox) - self._master.update_idletasks() @_BackendTk.export diff --git a/lib/matplotlib/backends/backend_tkcairo.py b/lib/matplotlib/backends/backend_tkcairo.py index 99204f8d5287..a81fd0d92bb8 100644 --- a/lib/matplotlib/backends/backend_tkcairo.py +++ b/lib/matplotlib/backends/backend_tkcairo.py @@ -23,7 +23,6 @@ def draw(self): _backend_tk.blit( self._tkphoto, buf, (2, 1, 0, 3) if sys.byteorder == "little" else (1, 2, 3, 0)) - self._master.update_idletasks() @_BackendTk.export From 23b0edfa932cb30ecae04c1dc4c0d84d84082284 Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Sun, 28 Jun 2020 13:20:51 -0400 Subject: [PATCH 2/9] resize_event eventually calls draw_idle No need to draw if event loop is responsive --- lib/matplotlib/backends/_backend_tk.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 6ac3ef32afba..79b9efef3cbe 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -232,7 +232,6 @@ def resize(self, event): self._tkcanvas.create_image( int(width / 2), int(height / 2), image=self._tkphoto) self.resize_event() - self.draw() def draw_idle(self): # docstring inherited From db31323d47e77fd4a55538acab92cced0961db4e Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Sun, 28 Jun 2020 13:25:37 -0400 Subject: [PATCH 3/9] Unnecessary method override for del message will be destroyed via tk object tree (master=self) and GCd by Python (only referred to by other self attributes) --- lib/matplotlib/backends/_backend_tk.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 79b9efef3cbe..8c4d3c895602 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -525,10 +525,6 @@ def __init__(self, canvas, window, *, pack_toolbar=True): if pack_toolbar: self.pack(side=tk.BOTTOM, fill=tk.X) - def destroy(self, *args): - del self.message - tk.Frame.destroy(self, *args) - def set_message(self, s): self.message.set(s) From 630e806ef7dfd0520490eeb85d2c866d8f8dca94 Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Sun, 28 Jun 2020 13:46:25 -0400 Subject: [PATCH 4/9] override FigureCanvasTk start_event_loop and stop_event_loop tkinter has a perfectly good event loop written in C --- lib/matplotlib/backends/_backend_tk.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 8c4d3c895602..cb33fca7d896 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -381,6 +381,16 @@ def flush_events(self): # docstring inherited self._master.update() + def start_event_loop(self, timeout=0): + # docstring inherited + if timeout > 0: + self._master.after(timeout, self.stop_event_loop) + self._master.mainloop() + + def stop_event_loop(self): + # docstring inherited + self._master.quit() + class FigureManagerTk(FigureManagerBase): """ From c94c5f9ba8d99e2ccb52a93e2f09936bb35f9219 Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Sat, 4 Jul 2020 07:43:39 -0400 Subject: [PATCH 5/9] Test update is never called --- lib/matplotlib/tests/test_backends_interactive.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 5bd06baa8b26..f7df2e44bf0b 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -194,3 +194,18 @@ def test_webagg(): conn.close() proc.send_signal(signal.SIGINT) assert proc.wait(timeout=_test_timeout) == 0 + + +@pytest.mark.backend('TkAgg', skip_on_importerror=True) +def test_never_update(monkeypatch): + import tkinter + monkeypatch.delattr(tkinter.Misc, 'update') + monkeypatch.delattr(tkinter.Misc, 'update_idletasks') + + import matplotlib.pyplot as plt + plt.plot([1, 2, 3], [1, 3, 5]) + plt.show(block=False) + fig = plt.gcf() + fig.canvas.toolbar.configure_subplots() + # skirt monkeypatch + tkinter._default_root.tk.call('update') From c53570db3e602d2855813fa76a971ae3b62d361a Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Tue, 14 Jul 2020 12:09:10 -0400 Subject: [PATCH 6/9] Correct start_event_loop behavior timeout is given in float seconds according to FigureCanvasBase, but tk needs int milliseconds --- lib/matplotlib/backends/_backend_tk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index cb33fca7d896..443e2b1e5e63 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -384,7 +384,7 @@ def flush_events(self): def start_event_loop(self, timeout=0): # docstring inherited if timeout > 0: - self._master.after(timeout, self.stop_event_loop) + self._master.after(int(1000*timeout), self.stop_event_loop) self._master.mainloop() def stop_event_loop(self): From 880c6c50369056e91e2614c8fed5e6ae0adbc161 Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Sat, 8 Aug 2020 15:07:58 -0400 Subject: [PATCH 7/9] pause for a short time rather than sneaky update start_event_loop semantics are equivalent to update with timeout < 0.001 --- lib/matplotlib/tests/test_backends_interactive.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index f7df2e44bf0b..50279d89afed 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -207,5 +207,4 @@ def test_never_update(monkeypatch): plt.show(block=False) fig = plt.gcf() fig.canvas.toolbar.configure_subplots() - # skirt monkeypatch - tkinter._default_root.tk.call('update') + plt.pause(1e-9) From 9bc82fb5d187f5fc716da0138b99d073625dd283 Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Tue, 11 Aug 2020 11:20:29 -0400 Subject: [PATCH 8/9] tighten and comment test_never_update --- .../tests/test_backends_interactive.py | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 50279d89afed..5ef94492ae05 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -197,14 +197,30 @@ def test_webagg(): @pytest.mark.backend('TkAgg', skip_on_importerror=True) -def test_never_update(monkeypatch): +def test_never_update(monkeypatch, capsys): import tkinter monkeypatch.delattr(tkinter.Misc, 'update') monkeypatch.delattr(tkinter.Misc, 'update_idletasks') import matplotlib.pyplot as plt - plt.plot([1, 2, 3], [1, 3, 5]) + fig = plt.figure() plt.show(block=False) - fig = plt.gcf() + + # regression test on FigureCanvasTkAgg + plt.draw() + # regression test on NavigationToolbar2Tk fig.canvas.toolbar.configure_subplots() - plt.pause(1e-9) + + # check for update() or update_idletasks() in the event queue + # functionally equivalent to tkinter.Misc.update + # must pause >= 1 ms to process tcl idle events plus + # extra time to avoid flaky tests on slow systems + plt.pause(0.1) + + # regression test on FigureCanvasTk filter_destroy callback + plt.close(fig) + + # test framework doesn't see tkinter callback exceptions normally + # see tkinter.Misc.report_callback_exception + assert "Exception in Tkinter callback" not in capsys.readouterr().err + From 502dd118e68d9c7d90ed3f6f43a9dffb1b19f933 Mon Sep 17 00:00:00 2001 From: Richard Sheridan Date: Tue, 11 Aug 2020 11:24:24 -0400 Subject: [PATCH 9/9] linting --- lib/matplotlib/tests/test_backends_interactive.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 5ef94492ae05..27b8793b5597 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -223,4 +223,3 @@ def test_never_update(monkeypatch, capsys): # test framework doesn't see tkinter callback exceptions normally # see tkinter.Misc.report_callback_exception assert "Exception in Tkinter callback" not in capsys.readouterr().err -