Skip to content

Backport PR #17391 on branch v3.2.x #17457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/matplotlib/backends/_backend_tk.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,8 +538,12 @@ def release(self, event):

def set_cursor(self, cursor):
window = self.canvas.get_tk_widget().master
window.configure(cursor=cursord[cursor])
window.update_idletasks()
try:
window.configure(cursor=cursord[cursor])
except tkinter.TclError:
pass
else:
window.update_idletasks()

def _Button(self, text, file, command, extension='.gif'):
img_file = str(cbook._get_data_path('images', file + extension))
Expand Down
22 changes: 12 additions & 10 deletions lib/matplotlib/backends/backend_wx.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,9 @@ def gui_repaint(self, drawDC=None, origin='WX'):
The 'WXAgg' backend sets origin accordingly.
"""
DEBUG_MSG("gui_repaint()", 1, self)
if self.IsShownOnScreen():
# The "if self" check avoids a "wrapped C/C++ object has been deleted"
# RuntimeError if doing things after window is closed.
if self and self.IsShownOnScreen():
if not drawDC:
# not called from OnPaint use a ClientDC
drawDC = wx.ClientDC(self)
Expand Down Expand Up @@ -978,14 +980,11 @@ def _print_image(self, filename, filetype, *args, **kwargs):

# Now that we have rendered into the bitmap, save it to the appropriate
# file type and clean up.
if isinstance(filename, str):
if not image.SaveFile(filename, filetype):
raise RuntimeError(f'Could not save figure to {filename}')
elif cbook.is_writable_file_like(filename):
if not isinstance(image, wx.Image):
image = image.ConvertToImage()
if not image.SaveStream(filename, filetype):
raise RuntimeError(f'Could not save figure to {filename}')
if (cbook.is_writable_file_like(filename) and
not isinstance(image, wx.Image)):
image = image.ConvertToImage()
if not image.SaveFile(filename, filetype):
raise RuntimeError(f'Could not save figure to {filename}')

# Restore everything to normal
self.bitmap = origBitmap
Expand All @@ -997,7 +996,10 @@ def _print_image(self, filename, filetype, *args, **kwargs):
# otherwise.
if self._isDrawn:
self.draw()
self.Refresh()
# The "if self" check avoids a "wrapped C/C++ object has been deleted"
# RuntimeError if doing things after window is closed.
if self:
self.Refresh()


########################################################################
Expand Down
27 changes: 23 additions & 4 deletions lib/matplotlib/tests/test_backends_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def _get_testable_interactive_backends():
_test_script = """\
import importlib
import importlib.util
import io
import sys
from unittest import TestCase

Expand Down Expand Up @@ -107,17 +108,34 @@ def check_alt_backend(alt_backend):
# Trigger quitting upon draw.
fig.canvas.mpl_connect("draw_event", lambda event: timer.start())

result = io.BytesIO()
fig.savefig(result, format='png')

plt.show()

# Ensure that the window is really closed.
plt.pause(0.5)

# Test that saving works after interactive window is closed, but the figure is
# not deleted.
result_after = io.BytesIO()
fig.savefig(result_after, format='png')

if not backend.startswith('qt5') and sys.platform == 'darwin':
# FIXME: This should be enabled everywhere once Qt5 is fixed on macOS to
# not resize incorrectly.
assert_equal(result.getvalue(), result_after.getvalue())
"""
_test_timeout = 10 # Empirically, 1s is not enough on Travis.


@pytest.mark.parametrize("backend", _get_testable_interactive_backends())
@pytest.mark.flaky(reruns=3)
def test_interactive_backend(backend):
proc = subprocess.run([sys.executable, "-c", _test_script],
env={**os.environ, "MPLBACKEND": backend},
timeout=_test_timeout)
proc = subprocess.run(
[sys.executable, "-c", _test_script],
env={**os.environ, "MPLBACKEND": backend, "SOURCE_DATE_EPOCH": "0"},
timeout=_test_timeout)
if proc.returncode:
pytest.fail("The subprocess returned with non-zero exit status "
f"{proc.returncode}.")
Expand All @@ -129,7 +147,8 @@ def test_interactive_backend(backend):
def test_webagg():
pytest.importorskip("tornado")
proc = subprocess.Popen([sys.executable, "-c", _test_script],
env={**os.environ, "MPLBACKEND": "webagg"})
env={**os.environ, "MPLBACKEND": "webagg",
"SOURCE_DATE_EPOCH": "0"})
url = "http://{}:{}".format(
mpl.rcParams["webagg.address"], mpl.rcParams["webagg.port"])
timeout = time.perf_counter() + _test_timeout
Expand Down