Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Use a GtkApplication in GTK backend. #20284

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 2 commits into from
Jun 16, 2021
Merged

Conversation

QuLogic
Copy link
Member

@QuLogic QuLogic commented May 22, 2021

PR Summary

Also, stop using gtk_main* functions; these were deprecated in GTK3, and no longer exist in GTK4. The lower level GLib functions have always been there, and won't go away.

PR Checklist

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (run flake8 on changed files to check).
  • [n/a] New features are documented, with examples if plot related.
  • [n/a] Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • Conforms to Matplotlib style conventions (install flake8-docstrings and run flake8 --docstring-convention=all).
  • [n/a] New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • [n/a] API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).

@QuLogic QuLogic added this to the v3.5.0 milestone May 22, 2021

_application.run() # Quits when all added windows close.
# Running after quit is undefined, so create a new one next time.
_application = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat of an edge case, but say someone sets up their own GtkApplication and this gets picked up by _create_application(), and then the application quits, and then we somehow reach again mainloop(). If I understand correctly, we need to set up a new GtkApplication in that case, because the old one will be invalid? If Gtk provides no builtin way to know whether a GtkApplication has already been run, should _create_application e.g. connect to the shutdown signal to add a flag to the application when it shuts down? Then the whole logic of whether new GtkApplications need to be created can be encapsulated in _create_application.

(Or I may have understood everything wrong...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we add the windows to the GtkApplication, so if we create a new one here, it'd just exit immediately without windows attached. So they'd have to create a new window, and that would create a new application, I think? We could also exit early here if there's no application; is that needed if plt.show is called without any windows?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think(?) you missed my point (which upon reading again my message was somewhat unclear), which is mostly that you cannot tell a priori whether _application (if returned by Gio.Application.get_default()) is a fresh GtkApplication or an already used one (i.e. one on which run() has already been called and exited). Or perhaps you can?

(Looking at it again, the point about shutdown was kind of a tangent.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think you can tell that.

That's also why I only put _create_application in FigureManager, because I thought that'd only be called if we're managing the mainloop entirely. Checking for a default was a copy from Qt, but in this way, I think that it can't even happen?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case (also re: #20284 (comment)), does that mean this part (resetting _application to None so that a later call to _create_application() creates a new one) can/should be removed?

Copy link
Member Author

@QuLogic QuLogic Jun 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all windows are closed, then this (and thus pyplot.show) returns, so we still need to set it to None so we can get a new application for any subsequent windows. Otherwise, re-using a previously-run application will crash, as noted below.

@QuLogic
Copy link
Member Author

QuLogic commented Jun 1, 2021

I did some testing, and while you can create multiple applications, you can only register one. And you can only add windows after the application has been registered. So we can't just create our own application 'just in case'. Also, if you add a window to an application that has already been run, it might crash, so we want to avoid that.

Copy link
Contributor

@anntzer anntzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really follow everything re: the GtkApplication life cycle, but this seems reasonable enough...

@QuLogic
Copy link
Member Author

QuLogic commented Jun 1, 2021

To clarify,

  1. You create a GtkApplication
  2. You .register() said application; this claims the DBus name for uniqueness, but that's irrelevant for us since we pass the NON_UNIQUE flag. You can only register one application at a time.
  3. You can then add windows to the application.
  4. application.run() starts the main loop and watches the added windows.
  5. When all added windows are closed, the application returns from .run.
  6. From documentation, running the application again is undefined, and from testing, adding a window to a previously-run application crashes.

As implemented, this:

  1. Creates a GtkApplication whenever a FigureManagerGTK is created, if there isn't one.
  2. Adds the window backing the FigureManagerGTK to the application.
  3. Runs said application in mainloop() for pyplot.show
  4. Deletes the application after the mainloop is complete, so that a new one is made when new windows are created, avoiding the undefined behaviour/crashing.

All our embedding examples use FigureCanvas* directly without FigureManager*, so this + the unique-registration requirement is why I did not attempt to create an application in FigureCanvasGTK as is done in the Qt backend.

@anntzer
Copy link
Contributor

anntzer commented Jun 1, 2021

Thanks for the detailed writeup. My concerns were about GtkApplications that (in embedding situations) may exist before matplotlib is even started (and may or may not have run yet) or that will be instantiated after matplotlib has exited (a gtk event loop), but if I understand correctly we can't do much about them anyways.

@QuLogic
Copy link
Member Author

QuLogic commented Jun 1, 2021

I think we mostly don't have to worry about that. If you embed in the way we suggest, you'd have a FigureCanvasGTK and this never touches applications. You'd add it to your window manually, and if using GtkApplication, can add the window to it as needed.

If you instead use FigureManagerGTK, then this should add windows to your existing application. This is not the recommended way, but I think is still safe. When you run your application, the Matplotlib windows should then be functional as part of it.

If you also use pyplot.show, then we might accidentally run your application, but I think you're playing with fire there.

If there's an existing application that was already run, but not cleaned up, then we have no way to check that, I think, so not much we can do there.

Also, stop using `gtk_main*` functions; these were deprecated in GTK3,
and no longer exist in GTK4. The lower level GLib functions have always
been there, and won't go away.
@tacaswell
Copy link
Member

How does this integrate with the command line input hook functionality?

https://github.com/ipython/ipython/blob/master/IPython/terminal/pt_inputhooks/gtk3.py is how it works with gtk3, but if you can not re-run an application and you need to attach each window to the application, we are going to have to do something fancier here...

@QuLogic
Copy link
Member Author

QuLogic commented Jun 4, 2021

It mostly works, you can open and close multiple windows with plt.show in between, and even use plt.ion fine.

There is a bug if you are in plt.show and Ctrl-C out of it. This somehow shuts down the application, but doesn't close the windows, and for some reason I don't understand, crashes on opening a new figure after.

@QuLogic
Copy link
Member Author

QuLogic commented Jun 10, 2021

OK, I think I figured it out. If you Ctrl+C out of plt.show, the application quits, but is not unref'd, and remains as the default. So on the next figure, we ask for the default and get back the old used one. Additionally, PyGObject appears to have a bug and Gio.Application.set_default does not accept None like it should. I've worked around this by adding an attribute on the used-up application that we check and ignore as a default.

This now works in IPython with plt.show and closing all figure, plt.show and Ctrl+C out of it and the prompt, and plt.ion for interactive windows.

@jklymak jklymak requested a review from tacaswell June 11, 2021 05:13
@tacaswell tacaswell merged commit e039d69 into matplotlib:master Jun 16, 2021
@QuLogic QuLogic deleted the gtk-app branch June 16, 2021 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants