diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index a8feb75..72cfc80 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -493,6 +493,7 @@ Currently the magic system has the following functions:""", %gui qt5 # enable PyQt5 event loop integration %gui gtk # enable PyGTK event loop integration %gui gtk3 # enable Gtk3 event loop integration + %gui gtk4 # enable Gtk4 event loop integration %gui tk # enable Tk event loop integration %gui osx # enable Cocoa event loop integration # (requires %matplotlib 1.1) diff --git a/IPython/core/magics/pylab.py b/IPython/core/magics/pylab.py index 1a01c65..9ec441a 100644 --- a/IPython/core/magics/pylab.py +++ b/IPython/core/magics/pylab.py @@ -88,7 +88,7 @@ class PylabMagics(Magics): You can list the available backends using the -l/--list option:: In [4]: %matplotlib --list - Available matplotlib backends: ['osx', 'qt4', 'qt5', 'gtk3', 'notebook', 'wx', 'qt', 'nbagg', + Available matplotlib backends: ['osx', 'qt4', 'qt5', 'gtk3', 'gtk4', 'notebook', 'wx', 'qt', 'nbagg', 'gtk', 'tk', 'inline'] """ args = magic_arguments.parse_argstring(self.matplotlib, line) diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index 1ca2b92..8e3aade 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -16,6 +16,7 @@ backends = { "tk": "TkAgg", "gtk": "GTKAgg", "gtk3": "GTK3Agg", + "gtk4": "GTK4Agg", "wx": "WXAgg", "qt4": "Qt4Agg", "qt5": "Qt5Agg", @@ -42,10 +43,11 @@ backend2gui = dict(zip(backends.values(), backends.keys())) backend2gui['Qt4Agg'] = 'qt' # In the reverse mapping, there are a few extra valid matplotlib backends that # map to the same GUI support -backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk' -backend2gui['GTK3Cairo'] = 'gtk3' -backend2gui['WX'] = 'wx' -backend2gui['CocoaAgg'] = 'osx' +backend2gui["GTK"] = backend2gui["GTKCairo"] = "gtk" +backend2gui["GTK3Cairo"] = "gtk3" +backend2gui["GTK4Cairo"] = "gtk4" +backend2gui["WX"] = "wx" +backend2gui["CocoaAgg"] = "osx" # And some backends that don't need GUI integration del backend2gui["nbAgg"] del backend2gui["agg"] diff --git a/IPython/lib/inputhookgtk4.py b/IPython/lib/inputhookgtk4.py new file mode 100644 index 0000000..a872cee --- /dev/null +++ b/IPython/lib/inputhookgtk4.py @@ -0,0 +1,43 @@ +""" +Enable Gtk4 to be used interactively by IPython. +""" +# ----------------------------------------------------------------------------- +# Copyright (c) 2021, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +import sys + +from gi.repository import GLib + +# ----------------------------------------------------------------------------- +# Code +# ----------------------------------------------------------------------------- + + +class _InputHook: + def __init__(self, context): + self._quit = False + GLib.io_add_watch(sys.stdin, GLib.PRIORITY_DEFAULT, GLib.IO_IN, self.quit) + + def quit(self, *args, **kwargs): + self._quit = True + return False + + def run(self): + context = GLib.MainContext.default() + while not self._quit: + context.iteration(True) + + +def inputhook_gtk4(): + hook = _InputHook() + hook.run() + return 0 diff --git a/IPython/terminal/pt_inputhooks/__init__.py b/IPython/terminal/pt_inputhooks/__init__.py index 8917baf..69ff0ba 100644 --- a/IPython/terminal/pt_inputhooks/__init__.py +++ b/IPython/terminal/pt_inputhooks/__init__.py @@ -14,6 +14,7 @@ backends = [ "gtk", "gtk2", "gtk3", + "gtk4", "tk", "wx", "pyglet", diff --git a/IPython/terminal/pt_inputhooks/gtk4.py b/IPython/terminal/pt_inputhooks/gtk4.py new file mode 100644 index 0000000..009fbf1 --- /dev/null +++ b/IPython/terminal/pt_inputhooks/gtk4.py @@ -0,0 +1,27 @@ +""" +prompt_toolkit input hook for GTK 4. +""" + +from gi.repository import GLib + + +class _InputHook: + def __init__(self, context): + self._quit = False + GLib.io_add_watch( + context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, self.quit + ) + + def quit(self, *args, **kwargs): + self._quit = True + return False + + def run(self): + context = GLib.MainContext.default() + while not self._quit: + context.iteration(True) + + +def inputhook(context): + hook = _InputHook(context) + hook.run() diff --git a/docs/source/config/eventloops.rst b/docs/source/config/eventloops.rst index 04bdd9f..dd527a6 100644 --- a/docs/source/config/eventloops.rst +++ b/docs/source/config/eventloops.rst @@ -7,9 +7,9 @@ loop, so you can use both a GUI and an interactive prompt together. IPython supports a number of common GUI toolkits, but from IPython 3.0, it is possible to integrate other event loops without modifying IPython itself. -Supported event loops include ``qt4``, ``qt5``, ``gtk2``, ``gtk3``, ``wx``, -``osx`` and ``tk``. Make sure the event loop you specify matches the GUI -toolkit used by your own code. +Supported event loops include ``qt4``, ``qt5``, ``gtk2``, ``gtk3``, ``gtk4``, +``wx``, ``osx`` and ``tk``. Make sure the event loop you specify matches the +GUI toolkit used by your own code. To make IPython GUI event loop integration occur automatically at every startup, set the ``c.InteractiveShellApp.gui`` configuration key in your diff --git a/docs/source/interactive/reference.rst b/docs/source/interactive/reference.rst index 6ff0097..5f20394 100644 --- a/docs/source/interactive/reference.rst +++ b/docs/source/interactive/reference.rst @@ -44,7 +44,7 @@ the command-line by passing the full class name and a corresponding value; type <...snip...> --matplotlib= (InteractiveShellApp.matplotlib) Default: None - Choices: ['auto', 'gtk', 'gtk3', 'inline', 'nbagg', 'notebook', 'osx', 'qt', 'qt4', 'qt5', 'tk', 'wx'] + Choices: ['auto', 'gtk', 'gtk3', 'gtk4', 'inline', 'nbagg', 'notebook', 'osx', 'qt', 'qt4', 'qt5', 'tk', 'wx'] Configure matplotlib for interactive use with the default matplotlib backend. <...snip...> @@ -902,7 +902,8 @@ For users, enabling GUI event loop integration is simple. You simple use the %gui [GUINAME] With no arguments, ``%gui`` removes all GUI support. Valid ``GUINAME`` -arguments include ``wx``, ``qt``, ``qt5``, ``gtk``, ``gtk3`` and ``tk``. +arguments include ``wx``, ``qt``, ``qt5``, ``gtk``, ``gtk3`` ``gtk4``, and +``tk``. Thus, to use wxPython interactively and create a running :class:`wx.App` object, do:: diff --git a/examples/IPython Kernel/Index.ipynb b/examples/IPython Kernel/Index.ipynb index c494bd5..6da3e93 100644 --- a/examples/IPython Kernel/Index.ipynb +++ b/examples/IPython Kernel/Index.ipynb @@ -150,6 +150,7 @@ "  gui-glut.py
\n", "  gui-gtk.py
\n", "  gui-gtk3.py
\n", + "  gui-gtk4.py
\n", "  gui-pyglet.py
\n", "  gui-qt.py
\n", "  gui-tk.py
\n", @@ -160,6 +161,7 @@ " gui-glut.py\n", " gui-gtk.py\n", " gui-gtk3.py\n", + " gui-gtk4.py\n", " gui-pyglet.py\n", " gui-qt.py\n", " gui-tk.py\n", diff --git a/examples/IPython Kernel/Rich Output.ipynb b/examples/IPython Kernel/Rich Output.ipynb index 28c0520..d294daf 100644 --- a/examples/IPython Kernel/Rich Output.ipynb +++ b/examples/IPython Kernel/Rich Output.ipynb @@ -3180,6 +3180,7 @@ "  gui-glut.py
\n", "  gui-gtk.py
\n", "  gui-gtk3.py
\n", + "  gui-gtk4.py
\n", "  gui-pyglet.py
\n", "  gui-qt.py
\n", "  gui-tk.py
\n", @@ -3230,6 +3231,7 @@ " gui-glut.py\n", " gui-gtk.py\n", " gui-gtk3.py\n", + " gui-gtk4.py\n", " gui-pyglet.py\n", " gui-qt.py\n", " gui-tk.py\n", diff --git a/examples/IPython Kernel/gui/gui-gtk4.py b/examples/IPython Kernel/gui/gui-gtk4.py new file mode 100644 index 0000000..bb8c56b --- /dev/null +++ b/examples/IPython Kernel/gui/gui-gtk4.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +"""Simple Gtk example to manually test event loop integration. + +This is meant to run tests manually in ipython as: + +In [1]: %gui gtk4 + +In [2]: %run gui-gtk4.py +""" + +import gi + +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk, GLib # noqa + + +def hello_world(wigdet, data=None): + print("Hello World") + + +def close_request_cb(widget, data=None): + global running + running = False + + +running = True +window = Gtk.Window() +window.connect("close-request", close_request_cb) +button = Gtk.Button(label="Hello World") +button.connect("clicked", hello_world, None) + +window.set_child(button) +window.show() + +context = GLib.MainContext.default() +while running: + context.iteration(True)