Skip to content

[ENH]: Ctrl+C on a blocking show() should close the figure window #23385

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

Open
timhoffm opened this issue Jul 4, 2022 · 10 comments
Open

[ENH]: Ctrl+C on a blocking show() should close the figure window #23385

timhoffm opened this issue Jul 4, 2022 · 10 comments

Comments

@timhoffm
Copy link
Member

timhoffm commented Jul 4, 2022

Problem

Currently, Ctrl+C terminates the show() call, but the window stays visible.

Proposed solution

No response

@QuLogic
Copy link
Member

QuLogic commented Jul 4, 2022

It should already do that. If you're seeing alternate behaviour, that's backend specific, so you should specify.

@timhoffm
Copy link
Member Author

timhoffm commented Jul 5, 2022

Observed in IPython + Qt

@QuLogic
Copy link
Member

QuLogic commented Jul 5, 2022

On main or in a release? Qt was implemented in #13306.

@daniilS
Copy link
Contributor

daniilS commented Jul 5, 2022

The figure window stays open in REPL with the tkinter backend too

@timhoffm
Copy link
Member Author

timhoffm commented Jul 6, 2022

Tested on main + ipython +

  • qtagg: Ctrl+C ends the blocking show() call via a KeyboardInterrupt, but the figure window stays alive
  • tkagg: same as qtagg
  • wxagg: Ctrl+C does not end the blocking show() call

timhoffm added a commit to timhoffm/matplotlib that referenced this issue Oct 11, 2023
Addresses the Qt part of matplotlib#23385.

It appears that `qapp.quit()` does not automatically close the windows
of the app. We therefore do it explicitly.

A unit test for this would be quite complex. Test this
by hand by Ctrl+C in an interactive shell.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Oct 11, 2023
Addresses the Qt part of matplotlib#23385.

It appears that `qapp.quit()` does not automatically close the windows
of the app. We therefore do it explicitly.

A unit test for this would be quite complex. Test this
by hand by Ctrl+C in an interactive shell.
@timhoffm
Copy link
Member Author

Note on tkagg: I've tried to add

except KeyboardInterrupt:
    first_manager.window.destroy()

at

try:
first_manager.window.mainloop()
finally:
manager_class._owns_mainloop = False

and that indeed closes the window. But when trying to show the next figure, I get a stack trace

---------------------------------------------------------------------------
TclError                                  Traceback (most recent call last)
Cell In[5], line 1
----> 1 plt.show()

File ~/git/matplotlib/lib/matplotlib/pyplot.py:526, in show(*args, **kwargs)
    482 """
    483 Display all open figures.
    484 
   (...)
    523 explicitly there.
    524 """
    525 _warn_if_gui_out_of_main_thread()
--> 526 return _get_backend_mod().show(*args, **kwargs)

File ~/git/matplotlib/lib/matplotlib/backend_bases.py:3398, in _Backend.show(cls, block)
   3396 for manager in managers:
   3397     try:
-> 3398         manager.show()  # Emits a warning for non-interactive backend.
   3399     except NonGuiException as exc:
   3400         _api.warn_external(str(exc))

File ~/git/matplotlib/lib/matplotlib/backends/_backend_tk.py:561, in FigureManagerTk.show(self)
    559     self.canvas.draw_idle()
    560 if mpl.rcParams['figure.raise_window']:
--> 561     self.canvas.manager.window.attributes('-topmost', 1)
    562     self.canvas.manager.window.attributes('-topmost', 0)
    563 self._shown = True

File ~/anaconda3/envs/mpl-dev/lib/python3.11/tkinter/__init__.py:2032, in Wm.wm_attributes(self, *args)
   2014 """This subcommand returns or sets platform specific attributes
   2015 
   2016 The first form returns a list of the platform specific flags and
   (...)
   2029 On Unix, there are currently no special attribute values.
   2030 """
   2031 args = ('wm', 'attributes', self._w) + args
-> 2032 return self.tk.call(args)

TclError: can't invoke "wm" command: application has been destroye

It seems that Tk is not creating a new application for every plot. But I'm too little of a Tk expert to find out how to do this properly. Ping @daniilS do you know how to handle this?

@daniilS
Copy link
Contributor

daniilS commented Oct 12, 2023

I think the issue is that just calling first_manager.window.destroy() leaves the manager with a stale reference to the window. Replacing it with Gcf.destroy(first_manager) (which is called when the window is manually closed, see below) should work.

def destroy(*args):
Gcf.destroy(self)
self.window.protocol("WM_DELETE_WINDOW", destroy)

timhoffm added a commit to timhoffm/matplotlib that referenced this issue Oct 16, 2023
Addresses the Qt part of matplotlib#23385.

It appears that `qapp.quit()` does not automatically close the windows
of the app. We therefore do it explicitly.

A unit test for this would be quite complex. Test this
by hand by Ctrl+C in an interactive shell.
@tacaswell
Copy link
Member

We should try to avoid injecting Gcf into the backend modules if we can and should not assume that the window is managed by pyplot/Gcf.

matplotlib/mpl-gui#8 is an idea we probably have to implement in Matplotlib core (independent of what we do with mpl-gui) in that we need to add the ability to Figure (or CanvasBase) to inject a cleanup callback at initialization time the same way with Artist we inject a remove callback when we add it to the parent (which is also the source of one of our (many) circular references but that is neither here nor there).

timhoffm added a commit to timhoffm/matplotlib that referenced this issue Oct 18, 2023
Addresses the Qt part of matplotlib#23385.

It appears that `qapp.quit()` does not automatically close the windows
of the app. We therefore do it explicitly.

A unit test for this would be quite complex. Test this
by hand by Ctrl+C in an interactive shell.
@anntzer
Copy link
Contributor

anntzer commented Nov 4, 2023

@timhoffm re:

qtagg: Ctrl+C ends the blocking show() call via a KeyboardInterrupt, but the figure window stays alive

Were you testing with qt5 or qt6? I just noted https://doc.qt.io/qt-6/qtcore-changes-qt6.html#other-classes:

In Qt 5, QCoreApplication::quit() was equivalent to calling QCoreApplication::exit(). This just exited the main event loop.
In Qt 6, the method will instead try to close all top-level windows by posting a close event. The windows are free to cancel the shutdown process by ignoring the event.
Call QCoreApplication::exit() to keep the non-conditional behavior.

Perhaps(?) we may be able to simplify #27064 by just always calling exit() instead of quit()? (both also exist for QEventLoop)

@timhoffm
Copy link
Member Author

timhoffm commented Nov 5, 2023

I don't remember which Qt version I tested with. But as I read this, quit and exit are equivalent for Qt 5 and don't close windows there. So as long as we support Qt5, we still need to handle window closing ourselves. Furthermore in Qt6 quit() was changed to close the windows. So I suppose the Qt6-only simplification would be to drop window closing, but continue to use quit()?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants