From 65027d3f0e294e6298a9bb20c64874d8770447ad Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sat, 10 Feb 2024 10:46:57 +0100 Subject: [PATCH 01/18] Return filename from save_figure function --- lib/matplotlib/backend_bases.py | 9 ++++++++- lib/matplotlib/backend_bases.pyi | 2 +- lib/matplotlib/backends/_backend_tk.py | 1 + lib/matplotlib/backends/backend_gtk3.py | 1 + lib/matplotlib/backends/backend_macosx.py | 1 + lib/matplotlib/backends/backend_qt.py | 1 + lib/matplotlib/backends/backend_wx.py | 1 + 7 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d7430a4494fd..f92c378fe78f 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3272,7 +3272,14 @@ def on_tool_fig_close(e): return self.subplot_tool def save_figure(self, *args): - """Save the current figure.""" + """ + Save the current figure. + + Returns + ------- + str or `None` + The filepath of the saved figure. For GTK4 and Web backends it returns None. + """ raise NotImplementedError def update(self): diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index 075d87a6edd8..f07ef056eb03 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -441,7 +441,7 @@ class NavigationToolbar2: def push_current(self) -> None: ... subplot_tool: widgets.SubplotTool def configure_subplots(self, *args): ... - def save_figure(self, *args) -> None: ... + def save_figure(self, *args) -> str | None: ... def update(self) -> None: ... def set_history_buttons(self) -> None: ... diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 693499f4ca01..9b26aca3e887 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -884,6 +884,7 @@ def save_figure(self, *args): self.canvas.figure.savefig(fname, format=extension) except Exception as e: tkinter.messagebox.showerror("Error saving file", str(e)) + return fname def set_history_buttons(self): state_map = {True: tk.NORMAL, False: tk.DISABLED} diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index d6acd5547b85..ec2133d1d46c 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -383,6 +383,7 @@ def on_notify_filter(*args): type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) dialog.run() dialog.destroy() + return fname class ToolbarGTK3(ToolContainerBase, Gtk.Box): diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index adb5b5691b23..6ea437a90ca1 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -142,6 +142,7 @@ def save_figure(self, *args): if mpl.rcParams['savefig.directory']: mpl.rcParams['savefig.directory'] = os.path.dirname(filename) self.canvas.figure.savefig(filename) + return filename class FigureManagerMac(_macosx.FigureManager, FigureManagerBase): diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index db593ae77ded..1008d4af9648 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -839,6 +839,7 @@ def save_figure(self, *args): self, "Error saving file", str(e), QtWidgets.QMessageBox.StandardButton.Ok, QtWidgets.QMessageBox.StandardButton.NoButton) + return fname def set_history_buttons(self): can_backward = self._nav_stack._pos > 0 diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 8064511ac28a..2a79d0ed906f 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1149,6 +1149,7 @@ def save_figure(self, *args): caption='Matplotlib error') dialog.ShowModal() dialog.Destroy() + return path def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height From a53d5db3510bf9522f6b20f8cd7c5b7f558d949d Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sat, 10 Feb 2024 10:59:26 +0100 Subject: [PATCH 02/18] Update documentation in case no figure is saved --- lib/matplotlib/backend_bases.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index f92c378fe78f..02512d6bbe08 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3278,7 +3278,8 @@ def save_figure(self, *args): Returns ------- str or `None` - The filepath of the saved figure. For GTK4 and Web backends it returns None. + The filepath of the saved figure. For GTK4 and WebAgg backends it returns `None`. + Returns `None` if figure is not saved. """ raise NotImplementedError From 68196050df32fcdfdbee59dfea6d678e9042d0fe Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sat, 10 Feb 2024 11:21:09 +0100 Subject: [PATCH 03/18] add API change documentation --- doc/api/next_api_changes/behavior/27744-FM.rst | 6 ++++++ lib/matplotlib/backend_bases.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 doc/api/next_api_changes/behavior/27744-FM.rst diff --git a/doc/api/next_api_changes/behavior/27744-FM.rst b/doc/api/next_api_changes/behavior/27744-FM.rst new file mode 100644 index 000000000000..a5eafee9ca8a --- /dev/null +++ b/doc/api/next_api_changes/behavior/27744-FM.rst @@ -0,0 +1,6 @@ +``NavigationToolbar2.save_figure`` now returns filepath of saved figure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``NavigationToolbar2.save_figure`` function return the filename of the saved figure. +If no figure is save then it returns None. +For GTK4 backend and Web backend the still returns None. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 02512d6bbe08..6dc1cb898526 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3278,7 +3278,8 @@ def save_figure(self, *args): Returns ------- str or `None` - The filepath of the saved figure. For GTK4 and WebAgg backends it returns `None`. + The filepath of the saved figure. + For GTK4 and WebAgg backends it returns `None`. Returns `None` if figure is not saved. """ raise NotImplementedError From 2fd1310837b041bcaf78cb080c3be8fcc27402b6 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sat, 10 Feb 2024 14:44:30 +0100 Subject: [PATCH 04/18] Add some test --- lib/matplotlib/tests/test_backend_gtk3.py | 20 ++++++++++++++++++++ lib/matplotlib/tests/test_backend_macosx.py | 12 ++++++++++++ lib/matplotlib/tests/test_backend_qt.py | 11 +++++++++++ 3 files changed, 43 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_gtk3.py b/lib/matplotlib/tests/test_backend_gtk3.py index 6a95f47e1ddd..b4e298d7cc1c 100644 --- a/lib/matplotlib/tests/test_backend_gtk3.py +++ b/lib/matplotlib/tests/test_backend_gtk3.py @@ -1,6 +1,8 @@ +import os from matplotlib import pyplot as plt import pytest +from unittest import mock pytest.importorskip("matplotlib.backends.backend_gtk3agg") @@ -49,3 +51,21 @@ def receive(event): fig.canvas.mpl_connect("draw_event", send) fig.canvas.mpl_connect("key_press_event", receive) plt.show() + + +@pytest.mark.backend("gtk3agg", skip_on_importerror=True) +def test_save_figure_return(): + from gi.repository import Gtk + fig, ax = plt.subplots() + ax.imshow([[1]]) + with mock.patch("gi.repository.Gtk.FileFilter") as fileFilter: + filt = fileFilter.return_value + filt.get_name.return_value = "Portable Network Graphics" + with mock.patch("gi.repository.Gtk.FileChooserDialog") as dialogChooser: + dialog = dialogChooser.return_value + dialog.get_filter.return_value = filt + dialog.get_filename.return_value = "foobar.png" + dialog.run.return_value = Gtk.ResponseType.OK + fname = fig.canvas.manager.toolbar.save_figure() + os.remove("foobar.png") + assert fname == "foobar.png" diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index a4350fe3b6c6..a0e50836bf2a 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -1,6 +1,7 @@ import os import pytest +from unittest import mock import matplotlib as mpl import matplotlib.pyplot as plt @@ -49,3 +50,14 @@ def new_choose_save_file(title, directory, filename): def test_ipython(): from matplotlib.testing import ipython_in_subprocess ipython_in_subprocess("osx", "MacOSX", "macosx") + + +@pytest.mark.backend('macosx') +def test_save_figure_return(): + fig, ax = plt.subplots() + ax.imshow([[1]]) + prop = "matplotlib.backends._macosx.choose_save_file" + with mock.patch(prop, return_value="foobar.png"): + fname = fig.canvas.manager.toolbar.save_figure() + os.remove("foobar.png") + assert fname == "foobar.png" diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index 026a49b1441e..11684c79640e 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -227,6 +227,17 @@ def test_figureoptions(): fig.canvas.manager.toolbar.edit_parameters() +@pytest.mark.backend('QtAgg', skip_on_importerror=True) +def test_save_figure_return(): + fig, ax = plt.subplots() + ax.imshow([[1]]) + prop = "matplotlib.backends.qt_compat.QtWidgets.QFileDialog.getSaveFileName" + with mock.patch(prop, return_value=("foobar.png", None)): + fname = fig.canvas.manager.toolbar.save_figure() + os.remove("foobar.png") + assert fname == "foobar.png" + + @pytest.mark.backend('QtAgg', skip_on_importerror=True) def test_figureoptions_with_datetime_axes(): fig, ax = plt.subplots() From f4ef6eb0ec1120a85e486a87623d26847ff6e01f Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sat, 10 Feb 2024 20:20:25 +0100 Subject: [PATCH 05/18] Update doc/api/next_api_changes/behavior/27744-FM.rst Co-authored-by: Thomas A Caswell --- doc/api/next_api_changes/behavior/27744-FM.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/api/next_api_changes/behavior/27744-FM.rst b/doc/api/next_api_changes/behavior/27744-FM.rst index a5eafee9ca8a..3e44adc0b75c 100644 --- a/doc/api/next_api_changes/behavior/27744-FM.rst +++ b/doc/api/next_api_changes/behavior/27744-FM.rst @@ -1,6 +1,11 @@ ``NavigationToolbar2.save_figure`` now returns filepath of saved figure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``NavigationToolbar2.save_figure`` function return the filename of the saved figure. -If no figure is save then it returns None. -For GTK4 backend and Web backend the still returns None. +``NavigationToolbar2.save_figure`` function may return the filename of the saved figure. + +If a backend implements this +functinoality it should return `None` in the case where no +figure is actually saved (because the user closed the dialog without saving). + +If the backend does not or can not implement this functionality (currently the Gtk4 backends and webagg backends +do not) this this method will return `None` as it currently does. From 380ff99a78d64532ecea964011aba86dfb1ca963 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 11 Feb 2024 10:36:00 +0100 Subject: [PATCH 06/18] return special value when saved status is unknown --- doc/api/next_api_changes/behavior/27744-FM.rst | 7 +++---- lib/matplotlib/backend_bases.py | 2 ++ lib/matplotlib/backend_bases.pyi | 2 +- lib/matplotlib/backends/_backend_tk.py | 5 +++-- lib/matplotlib/backends/backend_gtk3.py | 2 +- lib/matplotlib/backends/backend_gtk4.py | 1 + lib/matplotlib/backends/backend_qt.py | 2 +- lib/matplotlib/backends/backend_webagg_core.py | 3 ++- lib/matplotlib/backends/backend_wx.py | 4 ++-- 9 files changed, 16 insertions(+), 12 deletions(-) diff --git a/doc/api/next_api_changes/behavior/27744-FM.rst b/doc/api/next_api_changes/behavior/27744-FM.rst index 3e44adc0b75c..7400ae151ae1 100644 --- a/doc/api/next_api_changes/behavior/27744-FM.rst +++ b/doc/api/next_api_changes/behavior/27744-FM.rst @@ -3,9 +3,8 @@ ``NavigationToolbar2.save_figure`` function may return the filename of the saved figure. -If a backend implements this -functinoality it should return `None` in the case where no +If a backend implements this functionality it should return `None` in the case where no figure is actually saved (because the user closed the dialog without saving). -If the backend does not or can not implement this functionality (currently the Gtk4 backends and webagg backends -do not) this this method will return `None` as it currently does. +If the backend does not or can not implement this functionality (currently the Gtk4 backends +and webagg backends do not) this this method will return `NavigationToolbar2.UNKNOWN_SAVED_STATUS`. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 6dc1cb898526..0fa223b0317b 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2858,6 +2858,8 @@ class NavigationToolbar2: ('Save', 'Save the figure', 'filesave', 'save_figure'), ) + UNKNOWN_SAVED_STATUS = object() + def __init__(self, canvas): self.canvas = canvas canvas.toolbar = self diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index f07ef056eb03..842e14535530 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -441,7 +441,7 @@ class NavigationToolbar2: def push_current(self) -> None: ... subplot_tool: widgets.SubplotTool def configure_subplots(self, *args): ... - def save_figure(self, *args) -> str | None: ... + def save_figure(self, *args) -> str | None | object: ... def update(self) -> None: ... def set_history_buttons(self) -> None: ... diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 9b26aca3e887..5348d08fd687 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -867,7 +867,7 @@ def save_figure(self, *args): ) if fname in ["", ()]: - return + return self.FILE_NOT_SAVED # Save dir for next time, unless empty str (i.e., use cwd). if initialdir != "": mpl.rcParams['savefig.directory'] = ( @@ -882,9 +882,10 @@ def save_figure(self, *args): try: self.canvas.figure.savefig(fname, format=extension) + return fname except Exception as e: tkinter.messagebox.showerror("Error saving file", str(e)) - return fname + return self.FILE_NOT_SAVED def set_history_buttons(self): state_map = {True: tk.NORMAL, False: tk.DISABLED} diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index ec2133d1d46c..94bebdd32d56 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -377,13 +377,13 @@ def on_notify_filter(*args): mpl.rcParams['savefig.directory'] = os.path.dirname(fname) try: self.canvas.figure.savefig(fname, format=fmt) + return fname except Exception as e: dialog = Gtk.MessageDialog( parent=self.canvas.get_toplevel(), message_format=str(e), type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) dialog.run() dialog.destroy() - return fname class ToolbarGTK3(ToolContainerBase, Gtk.Box): diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 7e73a4863212..9361a7229171 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -391,6 +391,7 @@ def on_response(dialog, response): msg.show() dialog.show() + return self.UNKNOWN_SAVED_STATUS class ToolbarGTK4(ToolContainerBase, Gtk.Box): diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 1008d4af9648..8d976fe5d027 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -834,12 +834,12 @@ def save_figure(self, *args): mpl.rcParams['savefig.directory'] = os.path.dirname(fname) try: self.canvas.figure.savefig(fname) + return fname except Exception as e: QtWidgets.QMessageBox.critical( self, "Error saving file", str(e), QtWidgets.QMessageBox.StandardButton.Ok, QtWidgets.QMessageBox.StandardButton.NoButton) - return fname def set_history_buttons(self): can_backward = self._nav_stack._pos > 0 diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 4ceac1699543..093ebe6b046d 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -403,8 +403,9 @@ def remove_rubberband(self): self.canvas.send_event("rubberband", x0=-1, y0=-1, x1=-1, y1=-1) def save_figure(self, *args): - """Save the current figure""" + """Save the current figure.""" self.canvas.send_event('save') + return self.UNKNOWN_SAVED_STATUS def pan(self): super().pan() diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 2a79d0ed906f..9ad67251c200 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1143,14 +1143,14 @@ def save_figure(self, *args): mpl.rcParams["savefig.directory"] = str(path.parent) try: self.canvas.figure.savefig(path, format=fmt) + return path except Exception as e: dialog = wx.MessageDialog( parent=self.canvas.GetParent(), message=str(e), caption='Matplotlib error') dialog.ShowModal() dialog.Destroy() - return path - + def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height sf = 1 if wx.Platform == '__WXMSW__' else self.canvas.GetDPIScaleFactor() From c8bf027a8821acf09f34a2e2ec7234ff5dc35203 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sat, 10 Feb 2024 20:21:57 +0100 Subject: [PATCH 07/18] Update lib/matplotlib/backend_bases.py Co-authored-by: Thomas A Caswell --- lib/matplotlib/backend_bases.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 0fa223b0317b..9b589f37a89a 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3276,13 +3276,18 @@ def on_tool_fig_close(e): def save_figure(self, *args): """ Save the current figure. + + Backend implementations may choose to return + the absolute path of the saved file, if any, as + a string. + + If no file is created or the backend does not implement + this functionality then `None` is returned. Returns ------- str or `None` The filepath of the saved figure. - For GTK4 and WebAgg backends it returns `None`. - Returns `None` if figure is not saved. """ raise NotImplementedError From fb5551285f9e2d30bed7b32e9d16c6f10233199d Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 11 Feb 2024 15:37:36 +0100 Subject: [PATCH 08/18] correction on tk backend --- doc/api/next_api_changes/behavior/27744-FM.rst | 4 ++-- lib/matplotlib/backends/_backend_tk.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/next_api_changes/behavior/27744-FM.rst b/doc/api/next_api_changes/behavior/27744-FM.rst index 7400ae151ae1..f9bc93d74ba6 100644 --- a/doc/api/next_api_changes/behavior/27744-FM.rst +++ b/doc/api/next_api_changes/behavior/27744-FM.rst @@ -6,5 +6,5 @@ If a backend implements this functionality it should return `None` in the case where no figure is actually saved (because the user closed the dialog without saving). -If the backend does not or can not implement this functionality (currently the Gtk4 backends -and webagg backends do not) this this method will return `NavigationToolbar2.UNKNOWN_SAVED_STATUS`. +If the backend does not or can not implement this functionality (currently the Gtk4 backends +and webagg backends do not) this this method will return ``NavigationToolbar2.UNKNOWN_SAVED_STATUS``. diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 5348d08fd687..5ed14dae0ef1 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -867,7 +867,7 @@ def save_figure(self, *args): ) if fname in ["", ()]: - return self.FILE_NOT_SAVED + return # Save dir for next time, unless empty str (i.e., use cwd). if initialdir != "": mpl.rcParams['savefig.directory'] = ( From 279919041c9c055fc28dbd57276ce1d9db54ce62 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 11 Feb 2024 16:03:33 +0100 Subject: [PATCH 09/18] return NavigationToolbar2.NO_FILE_SAVED when no file is saved --- doc/api/next_api_changes/behavior/27744-FM.rst | 6 +++--- lib/matplotlib/backend_bases.py | 15 ++++++++++----- lib/matplotlib/backend_bases.pyi | 1 + lib/matplotlib/backends/backend_gtk3.py | 3 ++- lib/matplotlib/backends/backend_gtk4.py | 1 - lib/matplotlib/backends/backend_macosx.py | 2 +- lib/matplotlib/backends/backend_qt.py | 1 + lib/matplotlib/backends/backend_webagg_core.py | 1 - lib/matplotlib/backends/backend_wx.py | 3 ++- lib/matplotlib/tests/test_backend_tk.py | 13 +++++++++++++ 10 files changed, 33 insertions(+), 13 deletions(-) diff --git a/doc/api/next_api_changes/behavior/27744-FM.rst b/doc/api/next_api_changes/behavior/27744-FM.rst index f9bc93d74ba6..17de91e84a53 100644 --- a/doc/api/next_api_changes/behavior/27744-FM.rst +++ b/doc/api/next_api_changes/behavior/27744-FM.rst @@ -3,8 +3,8 @@ ``NavigationToolbar2.save_figure`` function may return the filename of the saved figure. -If a backend implements this functionality it should return `None` in the case where no -figure is actually saved (because the user closed the dialog without saving). +If a backend implements this functionality it should return ``NavigationToolbar2.NO_FILE_SAVED`` +in the case where no figure is actually saved (because the user closed the dialog without saving). If the backend does not or can not implement this functionality (currently the Gtk4 backends -and webagg backends do not) this this method will return ``NavigationToolbar2.UNKNOWN_SAVED_STATUS``. +and webagg backends do not) this this method will return `None`. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 9b589f37a89a..8279ad3538fd 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2858,7 +2858,7 @@ class NavigationToolbar2: ('Save', 'Save the figure', 'filesave', 'save_figure'), ) - UNKNOWN_SAVED_STATUS = object() + NO_FILE_SAVED = object() def __init__(self, canvas): self.canvas = canvas @@ -3280,14 +3280,19 @@ def save_figure(self, *args): Backend implementations may choose to return the absolute path of the saved file, if any, as a string. - - If no file is created or the backend does not implement - this functionality then `None` is returned. + + If no file is created then `NavigationToolbar2.NO_FILE_SAVED` + is returned. + + If the backend does not implement this functionality + then `None` is returned. Returns ------- - str or `None` + str or `NavigationToolbar2.NO_FILE_SAVED` or `None` The filepath of the saved figure. + Returns `NavigationToolbar2.NO_FILE_SAVED` if figure is not saved. + Returns `None` when the backend does not provide the information. """ raise NotImplementedError diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index 842e14535530..efd7d1bfcaed 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -406,6 +406,7 @@ class _Mode(str, Enum): class NavigationToolbar2: toolitems: tuple[tuple[str, ...] | tuple[None, ...], ...] + NO_FILE_SAVED: object canvas: FigureCanvasBase mode: _Mode def __init__(self, canvas: FigureCanvasBase) -> None: ... diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 94bebdd32d56..a058bda9de29 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -371,7 +371,7 @@ def on_notify_filter(*args): fmt = self.canvas.get_supported_filetypes_grouped()[ff.get_name()][0] dialog.destroy() if response != Gtk.ResponseType.OK: - return + return self.NO_FILE_SAVED # Save dir for next time, unless empty str (which means use cwd). if mpl.rcParams['savefig.directory']: mpl.rcParams['savefig.directory'] = os.path.dirname(fname) @@ -384,6 +384,7 @@ def on_notify_filter(*args): type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) dialog.run() dialog.destroy() + return self.NO_FILE_SAVED class ToolbarGTK3(ToolContainerBase, Gtk.Box): diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 9361a7229171..7e73a4863212 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -391,7 +391,6 @@ def on_response(dialog, response): msg.show() dialog.show() - return self.UNKNOWN_SAVED_STATUS class ToolbarGTK4(ToolContainerBase, Gtk.Box): diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 6ea437a90ca1..2a7e1b01a841 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -137,7 +137,7 @@ def save_figure(self, *args): directory, self.canvas.get_default_filename()) if filename is None: # Cancel - return + return self.NO_FILE_SAVED # Save dir for next time, unless empty str (which means use cwd). if mpl.rcParams['savefig.directory']: mpl.rcParams['savefig.directory'] = os.path.dirname(filename) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 8d976fe5d027..c46fd0f41bbb 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -840,6 +840,7 @@ def save_figure(self, *args): self, "Error saving file", str(e), QtWidgets.QMessageBox.StandardButton.Ok, QtWidgets.QMessageBox.StandardButton.NoButton) + return self.NO_FILE_SAVED def set_history_buttons(self): can_backward = self._nav_stack._pos > 0 diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 093ebe6b046d..c24a050806de 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -405,7 +405,6 @@ def remove_rubberband(self): def save_figure(self, *args): """Save the current figure.""" self.canvas.send_event('save') - return self.UNKNOWN_SAVED_STATUS def pan(self): super().pan() diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 9ad67251c200..79c562259ca8 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1150,7 +1150,8 @@ def save_figure(self, *args): caption='Matplotlib error') dialog.ShowModal() dialog.Destroy() - + return self.NO_FILE_SAVED + def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height sf = 1 if wx.Platform == '__WXMSW__' else self.canvas.GetDPIScaleFactor() diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index ee20a94042f7..3b84a5a634c5 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -196,6 +196,19 @@ class Toolbar(NavigationToolbar2Tk): print("success") +@_isolated_tk_test(success_count=1) +def test_save_figure_return(): + import matplotlib.pyplot as plt + from unittest import mock + fig = plt.figure() + prop = "tkinter.filedialog.asksaveasfilename" + with mock.patch(prop, return_value="foobar.png"): + fname = fig.canvas.manager.toolbar.save_figure() + os.remove("foobar.png") + assert fname == "foobar.png" + print("success") + + @_isolated_tk_test(success_count=1) def test_canvas_focus(): import tkinter as tk From 1e278ec6e87fbc5ebad899f7c511dcd7c35583b0 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 5 May 2024 18:28:38 +0200 Subject: [PATCH 10/18] None is for no file saved --- doc/api/next_api_changes/behavior/27744-FM.rst | 6 +++--- lib/matplotlib/backend_bases.py | 15 +++++++-------- lib/matplotlib/backend_bases.pyi | 2 +- lib/matplotlib/backends/_backend_tk.py | 1 - lib/matplotlib/backends/backend_gtk3.py | 3 +-- lib/matplotlib/backends/backend_gtk4.py | 1 + lib/matplotlib/backends/backend_macosx.py | 2 +- lib/matplotlib/backends/backend_qt.py | 3 +-- lib/matplotlib/backends/backend_webagg_core.py | 1 + lib/matplotlib/backends/backend_wx.py | 1 - 10 files changed, 16 insertions(+), 19 deletions(-) diff --git a/doc/api/next_api_changes/behavior/27744-FM.rst b/doc/api/next_api_changes/behavior/27744-FM.rst index 17de91e84a53..ae0d86336f81 100644 --- a/doc/api/next_api_changes/behavior/27744-FM.rst +++ b/doc/api/next_api_changes/behavior/27744-FM.rst @@ -1,10 +1,10 @@ ``NavigationToolbar2.save_figure`` now returns filepath of saved figure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``NavigationToolbar2.save_figure`` function may return the filename of the saved figure. +``NavigationToolbar2.save_figure`` function may return the filename of the saved figure. -If a backend implements this functionality it should return ``NavigationToolbar2.NO_FILE_SAVED`` +If a backend implements this functionality it should return `None` in the case where no figure is actually saved (because the user closed the dialog without saving). If the backend does not or can not implement this functionality (currently the Gtk4 backends -and webagg backends do not) this this method will return `None`. +and webagg backends do not) this method will return ``NavigationToolbar2.UNKNOWN_SAVED_STATUS``. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 8279ad3538fd..bca1ef3f1708 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2858,7 +2858,7 @@ class NavigationToolbar2: ('Save', 'Save the figure', 'filesave', 'save_figure'), ) - NO_FILE_SAVED = object() + UNKNOWN_SAVED_STATUS = object() def __init__(self, canvas): self.canvas = canvas @@ -3276,23 +3276,22 @@ def on_tool_fig_close(e): def save_figure(self, *args): """ Save the current figure. - + Backend implementations may choose to return the absolute path of the saved file, if any, as a string. - If no file is created then `NavigationToolbar2.NO_FILE_SAVED` - is returned. + If no file is created then `None` is returned. If the backend does not implement this functionality - then `None` is returned. + then `NavigationToolbar2.UNKNOWN_SAVED_STATUS` is returned. Returns ------- - str or `NavigationToolbar2.NO_FILE_SAVED` or `None` + str or `NavigationToolbar2.UNKNOWN_SAVED_STATUS` or `None` The filepath of the saved figure. - Returns `NavigationToolbar2.NO_FILE_SAVED` if figure is not saved. - Returns `None` when the backend does not provide the information. + Returns `None` if figure is not saved. + Returns `NavigationToolbar2.UNKNOWN_SAVED_STATUS` when the backend does not provide the information. """ raise NotImplementedError diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index efd7d1bfcaed..491a622b859a 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -406,7 +406,7 @@ class _Mode(str, Enum): class NavigationToolbar2: toolitems: tuple[tuple[str, ...] | tuple[None, ...], ...] - NO_FILE_SAVED: object + UNKNOWN_SAVED_STATUS: object canvas: FigureCanvasBase mode: _Mode def __init__(self, canvas: FigureCanvasBase) -> None: ... diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 5ed14dae0ef1..2472eacf5344 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -885,7 +885,6 @@ def save_figure(self, *args): return fname except Exception as e: tkinter.messagebox.showerror("Error saving file", str(e)) - return self.FILE_NOT_SAVED def set_history_buttons(self): state_map = {True: tk.NORMAL, False: tk.DISABLED} diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a058bda9de29..c0618cce81cf 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -371,7 +371,7 @@ def on_notify_filter(*args): fmt = self.canvas.get_supported_filetypes_grouped()[ff.get_name()][0] dialog.destroy() if response != Gtk.ResponseType.OK: - return self.NO_FILE_SAVED + return None # Save dir for next time, unless empty str (which means use cwd). if mpl.rcParams['savefig.directory']: mpl.rcParams['savefig.directory'] = os.path.dirname(fname) @@ -384,7 +384,6 @@ def on_notify_filter(*args): type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) dialog.run() dialog.destroy() - return self.NO_FILE_SAVED class ToolbarGTK3(ToolContainerBase, Gtk.Box): diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 7e73a4863212..9361a7229171 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -391,6 +391,7 @@ def on_response(dialog, response): msg.show() dialog.show() + return self.UNKNOWN_SAVED_STATUS class ToolbarGTK4(ToolContainerBase, Gtk.Box): diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 2a7e1b01a841..6ea437a90ca1 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -137,7 +137,7 @@ def save_figure(self, *args): directory, self.canvas.get_default_filename()) if filename is None: # Cancel - return self.NO_FILE_SAVED + return # Save dir for next time, unless empty str (which means use cwd). if mpl.rcParams['savefig.directory']: mpl.rcParams['savefig.directory'] = os.path.dirname(filename) diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index c46fd0f41bbb..df827db3fef4 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -834,13 +834,12 @@ def save_figure(self, *args): mpl.rcParams['savefig.directory'] = os.path.dirname(fname) try: self.canvas.figure.savefig(fname) - return fname except Exception as e: QtWidgets.QMessageBox.critical( self, "Error saving file", str(e), QtWidgets.QMessageBox.StandardButton.Ok, QtWidgets.QMessageBox.StandardButton.NoButton) - return self.NO_FILE_SAVED + return fname def set_history_buttons(self): can_backward = self._nav_stack._pos > 0 diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index c24a050806de..093ebe6b046d 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -405,6 +405,7 @@ def remove_rubberband(self): def save_figure(self, *args): """Save the current figure.""" self.canvas.send_event('save') + return self.UNKNOWN_SAVED_STATUS def pan(self): super().pan() diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 79c562259ca8..961e6b96f201 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1150,7 +1150,6 @@ def save_figure(self, *args): caption='Matplotlib error') dialog.ShowModal() dialog.Destroy() - return self.NO_FILE_SAVED def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height From 4023c320f8a7b783ffae0afd806e01a75043f7dc Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 5 May 2024 18:28:59 +0200 Subject: [PATCH 11/18] Also test None values --- lib/matplotlib/tests/test_backend_gtk3.py | 5 +++++ lib/matplotlib/tests/test_backend_macosx.py | 3 +++ lib/matplotlib/tests/test_backend_qt.py | 3 +++ lib/matplotlib/tests/test_backend_tk.py | 5 ++++- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backend_gtk3.py b/lib/matplotlib/tests/test_backend_gtk3.py index b4e298d7cc1c..e5b7269e9c35 100644 --- a/lib/matplotlib/tests/test_backend_gtk3.py +++ b/lib/matplotlib/tests/test_backend_gtk3.py @@ -69,3 +69,8 @@ def test_save_figure_return(): fname = fig.canvas.manager.toolbar.save_figure() os.remove("foobar.png") assert fname == "foobar.png" + + dialog.get_filename.return_value = None + dialog.run.return_value = Gtk.ResponseType.OK + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index a0e50836bf2a..ecbed0dcea3e 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -61,3 +61,6 @@ def test_save_figure_return(): fname = fig.canvas.manager.toolbar.save_figure() os.remove("foobar.png") assert fname == "foobar.png" + with mock.patch(prop, return_value=None): + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index 11684c79640e..86f8241aac9e 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -236,6 +236,9 @@ def test_save_figure_return(): fname = fig.canvas.manager.toolbar.save_figure() os.remove("foobar.png") assert fname == "foobar.png" + with mock.patch(prop, return_value=(None, None)): + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None @pytest.mark.backend('QtAgg', skip_on_importerror=True) diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index 3b84a5a634c5..632467e614c4 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -206,7 +206,10 @@ def test_save_figure_return(): fname = fig.canvas.manager.toolbar.save_figure() os.remove("foobar.png") assert fname == "foobar.png" - print("success") + with mock.patch(prop, return_value=None): + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None + print("success") @_isolated_tk_test(success_count=1) From 6c9c27780fe281c8667dd3169658d612bf7fe55e Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 5 May 2024 19:01:07 +0200 Subject: [PATCH 12/18] correction of line too long --- lib/matplotlib/backend_bases.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index bca1ef3f1708..f8976187c0b2 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3291,7 +3291,8 @@ def save_figure(self, *args): str or `NavigationToolbar2.UNKNOWN_SAVED_STATUS` or `None` The filepath of the saved figure. Returns `None` if figure is not saved. - Returns `NavigationToolbar2.UNKNOWN_SAVED_STATUS` when the backend does not provide the information. + Returns `NavigationToolbar2.UNKNOWN_SAVED_STATUS` when + the backend does not provide the information. """ raise NotImplementedError From 1588442cb24c40e823f6f82bd49b2964a96e75e4 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 5 May 2024 19:01:18 +0200 Subject: [PATCH 13/18] correction of tk backend --- lib/matplotlib/backends/_backend_tk.py | 2 +- lib/matplotlib/tests/test_backend_tk.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 2472eacf5344..6923f0b7507d 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -867,7 +867,7 @@ def save_figure(self, *args): ) if fname in ["", ()]: - return + return None # Save dir for next time, unless empty str (i.e., use cwd). if initialdir != "": mpl.rcParams['savefig.directory'] = ( diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index 632467e614c4..fceef6670d9e 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -206,7 +206,7 @@ def test_save_figure_return(): fname = fig.canvas.manager.toolbar.save_figure() os.remove("foobar.png") assert fname == "foobar.png" - with mock.patch(prop, return_value=None): + with mock.patch(prop, return_value=""): fname = fig.canvas.manager.toolbar.save_figure() assert fname is None print("success") From 2c55f80ac90377ee644e2d109c28ff04acd61d6b Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 12 May 2024 17:14:42 +0200 Subject: [PATCH 14/18] correction of Gtk3 deprecation warning --- lib/matplotlib/backends/backend_gtk3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index c0618cce81cf..def232503938 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -380,8 +380,8 @@ def on_notify_filter(*args): return fname except Exception as e: dialog = Gtk.MessageDialog( - parent=self.canvas.get_toplevel(), message_format=str(e), - type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) + parent=self.canvas.get_toplevel(), text=str(e), + message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) dialog.run() dialog.destroy() From 896e9158d53a1b3a11cdf78e624a154f99b3f966 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 12 May 2024 17:22:36 +0200 Subject: [PATCH 15/18] too many whitespaces --- lib/matplotlib/tests/test_backend_macosx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index a736635c9e45..5e79f2333c7f 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -46,6 +46,7 @@ def new_choose_save_file(title, directory, filename): # we added a subdirectory "test" assert mpl.rcParams["savefig.directory"] == f"{tmp_path}/test" + @pytest.mark.backend('macosx') def test_save_figure_return(): fig, ax = plt.subplots() @@ -58,8 +59,8 @@ def test_save_figure_return(): with mock.patch(prop, return_value=None): fname = fig.canvas.manager.toolbar.save_figure() assert fname is None - - + + def test_ipython(): from matplotlib.testing import ipython_in_subprocess ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"}) From 59ddc0f0673ee04be8a69bebe2f695c908991205 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 12 May 2024 17:47:43 +0200 Subject: [PATCH 16/18] fix GTk3 deprecation warning --- lib/matplotlib/backends/backend_gtk3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 2d4f52782b44..32b31a3bc87c 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -339,7 +339,7 @@ def __init__(self, canvas): def save_figure(self, *args): dialog = Gtk.FileChooserDialog( title="Save the figure", - parent=self.canvas.get_toplevel(), + transient_for=self.canvas.get_toplevel(), action=Gtk.FileChooserAction.SAVE, buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK), @@ -380,7 +380,7 @@ def on_notify_filter(*args): return fname except Exception as e: dialog = Gtk.MessageDialog( - parent=self.canvas.get_toplevel(), text=str(e), + transient_for=self.canvas.get_toplevel(), text=str(e), message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) dialog.run() dialog.destroy() From f870e7ace0d58a92c29176cd6695be069dff7367 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 19 May 2024 15:36:03 +0200 Subject: [PATCH 17/18] Correction of pytest calls --- lib/matplotlib/tests/test_backend_macosx.py | 1 + lib/matplotlib/tests/test_backend_qt.py | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index 5e79f2333c7f..2438d0a1dbae 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -61,6 +61,7 @@ def test_save_figure_return(): assert fname is None +@pytest.mark.backend('macosx') def test_ipython(): from matplotlib.testing import ipython_in_subprocess ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"}) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index c658c6dabf55..c9ff4f30cf37 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -388,6 +388,7 @@ def custom_handler(signum, frame): signal.signal(signal.SIGINT, original_handler) +@pytest.mark.backend('QtAgg', skip_on_importerror=True) def test_ipython(): from matplotlib.testing import ipython_in_subprocess ipython_in_subprocess("qt", {(8, 24): "qtagg", (8, 15): "QtAgg", (7, 0): "Qt5Agg"}) From 470f8327e9d1b181b11cfe6abc8c818b2fc624f8 Mon Sep 17 00:00:00 2001 From: FMasson <97910143+Zybulon@users.noreply.github.com> Date: Sun, 19 May 2024 19:01:39 +0200 Subject: [PATCH 18/18] temporary check --- lib/matplotlib/tests/test_backend_gtk3.py | 9 +++++---- lib/matplotlib/tests/test_backend_macosx.py | 12 ++++++------ lib/matplotlib/tests/test_backend_tk.py | 5 +++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_gtk3.py b/lib/matplotlib/tests/test_backend_gtk3.py index 7d970951a4e5..7eea3d0f59aa 100644 --- a/lib/matplotlib/tests/test_backend_gtk3.py +++ b/lib/matplotlib/tests/test_backend_gtk3.py @@ -67,7 +67,8 @@ def test_save_figure_return(): os.remove("foobar.png") assert fname == "foobar.png" - dialog.get_filename.return_value = None - dialog.run.return_value = Gtk.ResponseType.OK - fname = fig.canvas.manager.toolbar.save_figure() - assert fname is None + with mock.patch("gi.repository.Gtk.MessageDialog"): + dialog.get_filename.return_value = None + dialog.run.return_value = Gtk.ResponseType.OK + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index 2438d0a1dbae..8e50ddf84024 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -47,6 +47,12 @@ def new_choose_save_file(title, directory, filename): assert mpl.rcParams["savefig.directory"] == f"{tmp_path}/test" +@pytest.mark.backend('macosx') +def test_ipython(): + from matplotlib.testing import ipython_in_subprocess + ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"}) + + @pytest.mark.backend('macosx') def test_save_figure_return(): fig, ax = plt.subplots() @@ -59,9 +65,3 @@ def test_save_figure_return(): with mock.patch(prop, return_value=None): fname = fig.canvas.manager.toolbar.save_figure() assert fname is None - - -@pytest.mark.backend('macosx') -def test_ipython(): - from matplotlib.testing import ipython_in_subprocess - ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"}) diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index fceef6670d9e..5357d2da4ebd 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -196,7 +196,7 @@ class Toolbar(NavigationToolbar2Tk): print("success") -@_isolated_tk_test(success_count=1) +@_isolated_tk_test(success_count=2) def test_save_figure_return(): import matplotlib.pyplot as plt from unittest import mock @@ -206,10 +206,11 @@ def test_save_figure_return(): fname = fig.canvas.manager.toolbar.save_figure() os.remove("foobar.png") assert fname == "foobar.png" + print("success") with mock.patch(prop, return_value=""): fname = fig.canvas.manager.toolbar.save_figure() assert fname is None - print("success") + print("success") @_isolated_tk_test(success_count=1)