From 0ed289cffd20e49bc791ffb7f59b4220670d2e9f Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 4 Dec 2017 20:45:38 -0800 Subject: [PATCH] Support pgi as alternative gobject bindings. --- .travis.yml | 23 +++++++---- doc/glossary/index.rst | 33 ++++++++++----- .../next_whats_new/2017-12-04-AL-pgi.rst | 19 +++++++++ lib/matplotlib/backends/_gtk3_compat.py | 41 +++++++++++++++++++ lib/matplotlib/backends/backend_gtk3.py | 29 +++---------- lib/matplotlib/backends/backend_gtk3agg.py | 2 +- lib/matplotlib/backends/backend_gtk3cairo.py | 2 +- .../tests/test_backends_interactive.py | 27 +++++++----- 8 files changed, 121 insertions(+), 55 deletions(-) create mode 100644 doc/users/next_whats_new/2017-12-04-AL-pgi.rst create mode 100644 lib/matplotlib/backends/_gtk3_compat.py diff --git a/.travis.yml b/.travis.yml index 56901a44412d..c6971c322498 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,22 +19,25 @@ addons: - result_images.tar.bz2 apt: packages: + - cm-super + - dvipng + - gdb + - gir1.2-gtk-3.0 + - graphviz - inkscape - libav-tools - - gdb + - libcairo2 + - libgeos-dev + - libgirepository-1.0.1 + - lmodern - mencoder - - dvipng + - otf-freefont - pgf - - lmodern - - cm-super + - texlive-fonts-recommended - texlive-latex-base - texlive-latex-extra - - texlive-fonts-recommended - texlive-latex-recommended - texlive-xetex - - graphviz - - libgeos-dev - - otf-freefont env: global: @@ -123,6 +126,10 @@ install: # install was successful by trying to import the toolkit (sometimes, the # install appears to be successful but shared libraries cannot be loaded at # runtime, so an actual import is a better check). + pip install cairocffi pgi && + python -c 'import pgi as gi; gi.require_version("Gtk", "3.0"); from pgi.repository import Gtk' && + echo 'pgi is available' || + echo 'pgi is not available' pip install pyqt5 && python -c 'import PyQt5.QtCore' && echo 'PyQt5 is available' || diff --git a/doc/glossary/index.rst b/doc/glossary/index.rst index a4713567726a..487caed10f4a 100644 --- a/doc/glossary/index.rst +++ b/doc/glossary/index.rst @@ -8,8 +8,8 @@ Glossary .. glossary:: AGG - The Anti-Grain Geometry (`Agg `_) rendering engine, capable of rendering - high-quality images + The Anti-Grain Geometry (`Agg `_) rendering + engine, capable of rendering high-quality images Cairo The `Cairo graphics `_ engine @@ -20,7 +20,8 @@ Glossary provides extensions to the standard datetime module EPS - Encapsulated Postscript (`EPS `_) + Encapsulated Postscript (`EPS + `_) FreeType `FreeType `_ is a font rasterization @@ -32,7 +33,8 @@ Glossary The Gimp Drawing Kit for GTK+ GTK - The GIMP Toolkit (`GTK `_) graphical user interface library + The GIMP Toolkit (`GTK `_) graphical user interface + library JPG The Joint Photographic Experts Group (`JPEG @@ -47,13 +49,14 @@ Glossary deviation, fourier transforms, and convolutions. PDF - Adobe's Portable Document Format (`PDF `_) + Adobe's Portable Document Format (`PDF + `_) PNG Portable Network Graphics (`PNG - `_), a raster graphics format - that employs lossless data compression which is more suitable - for line art than the lossy jpg format. Unlike the gif format, + `_), a raster + graphics format that employs lossless data compression which is more + suitable for line art than the lossy jpg format. Unlike the gif format, png is not encumbered by requirements for a patent license. PS @@ -64,12 +67,24 @@ Glossary channel. PDF was designed in part as a next-generation document format to replace postscript + pgi + `pgi ` exists as a relatively + new Python wrapper to GTK3 and acts as a pure python alternative to + PyGObject. pgi still exists in its infancy, currently missing many + features of PyGObject. However Matplotlib does not use any of these + missing features. + pygtk `pygtk `_ provides python wrappers for the :term:`GTK` widgets library for use with the GTK or GTKAgg backend. Widely used on linux, and is often packages as 'python-gtk2' + PyGObject + Like :term:`pygtk`, `PyGObject ` provides + python wrappers for the :term:`GTK` widgets library; unlike pygtk, + PyGObject wraps GTK3 instead of the now obsolete GTK2. + pyqt `pyqt `_ provides python wrappers for the :term:`Qt` widgets library and is required by @@ -82,14 +97,12 @@ Glossary language widely used for scripting, application development, web application servers, scientific computing and more. - pytz `pytz `_ provides the Olson tz database in Python. it allows accurate and cross platform timezone calculations and solves the issue of ambiguous times at the end of daylight savings - Qt `Qt `__ is a cross-platform application framework for desktop and embedded development. diff --git a/doc/users/next_whats_new/2017-12-04-AL-pgi.rst b/doc/users/next_whats_new/2017-12-04-AL-pgi.rst new file mode 100644 index 000000000000..4f774bef6989 --- /dev/null +++ b/doc/users/next_whats_new/2017-12-04-AL-pgi.rst @@ -0,0 +1,19 @@ +PGI bindings for gtk3 +--------------------- + +The GTK3 backends can now use PGI_ instead of PyGObject_. PGI is a fairly +incomplete binding for GObject, thus its use is not recommended; its main +benefit is its availability on Travis (thus allowing CI testing for the gtk3agg +and gtk3cairo backends). + +The binding selection rules are as follows: +- if ``gi`` has already been imported, use it; else +- if ``pgi`` has already been imported, use it; else +- if ``gi`` can be imported, use it; else +- if ``pgi`` can be imported, use it; else +- error out. + +Thus, to force usage of PGI when both bindings are installed, import it first. + +.. _PGI: https://pgi.readthedocs.io/en/latest/ +.. _PyGObject: http://pygobject.readthedocs.io/en/latest/# diff --git a/lib/matplotlib/backends/_gtk3_compat.py b/lib/matplotlib/backends/_gtk3_compat.py new file mode 100644 index 000000000000..825fa2341c80 --- /dev/null +++ b/lib/matplotlib/backends/_gtk3_compat.py @@ -0,0 +1,41 @@ +""" +GObject compatibility loader; supports ``gi`` and ``pgi``. + +The binding selection rules are as follows: +- if ``gi`` has already been imported, use it; else +- if ``pgi`` has already been imported, use it; else +- if ``gi`` can be imported, use it; else +- if ``pgi`` can be imported, use it; else +- error out. + +Thus, to force usage of PGI when both bindings are installed, import it first. +""" + +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +import six + +import importlib +import sys + + +if "gi" in sys.modules: + import gi +elif "pgi" in sys.modules: + import pgi as gi +else: + try: + import gi + except ImportError: + try: + import pgi as gi + except ImportError: + raise ImportError("The Gtk3 backend requires PyGObject or pgi") + + +gi.require_version("Gtk", "3.0") +globals().update( + {name: + importlib.import_module("{}.repository.{}".format(gi.__name__, name)) + for name in ["GLib", "GObject", "Gtk", "Gdk"]}) diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 3e1225b8a0b1..606b01977cdd 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -7,39 +7,20 @@ import os import sys -try: - import gi -except ImportError: - raise ImportError("Gtk3 backend requires pygobject to be installed.") - -try: - gi.require_version("Gtk", "3.0") -except AttributeError: - raise ImportError( - "pygobject version too old -- it must have require_version") -except ValueError: - raise ImportError( - "Gtk3 backend requires the GObject introspection bindings for Gtk 3 " - "to be installed.") - -try: - from gi.repository import Gtk, Gdk, GObject, GLib -except ImportError: - raise ImportError("Gtk3 backend requires pygobject to be installed.") - import matplotlib +from matplotlib import ( + backend_tools, cbook, colors as mcolors, lines, rcParams) from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, - NavigationToolbar2, RendererBase, TimerBase, cursors) -from matplotlib.backend_bases import ToolContainerBase, StatusbarBase + NavigationToolbar2, RendererBase, StatusbarBase, TimerBase, + ToolContainerBase, cursors) from matplotlib.backend_managers import ToolManager from matplotlib.cbook import is_writable_file_like from matplotlib.figure import Figure from matplotlib.widgets import SubplotTool +from ._gtk3_compat import GLib, GObject, Gtk, Gdk -from matplotlib import ( - backend_tools, cbook, colors as mcolors, lines, rcParams) _log = logging.getLogger(__name__) diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index a8bce0c4e6fc..5d175ec7eccc 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -42,7 +42,7 @@ def on_draw_event(self, widget, ctx): else: bbox_queue = self._bbox_queue - if HAS_CAIRO_CFFI: + if HAS_CAIRO_CFFI and not isinstance(ctx, cairo.Context): ctx = cairo.Context._from_pointer( cairo.ffi.cast('cairo_t **', id(ctx) + object.__basicsize__)[0], diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 79ba1fc2d24d..f27d38ecdb26 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -12,7 +12,7 @@ class RendererGTK3Cairo(backend_cairo.RendererCairo): def set_context(self, ctx): - if HAS_CAIRO_CFFI: + if HAS_CAIRO_CFFI and not isinstance(ctx, cairo.Context): ctx = cairo.Context._from_pointer( cairo.ffi.cast( 'cairo_t **', diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 0a8b7d80276c..07d5759bbe0c 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -17,17 +17,22 @@ def _get_testable_interactive_backends(): - return [ - pytest.mark.skipif( - not os.environ.get("DISPLAY") - or sys.version_info < (3,) - or importlib.util.find_spec(module_name) is None, - reason="No $DISPLAY or could not import {!r}".format(module_name))( - backend) - for module_name, backend in [ - ("PyQt5", "qt5agg"), - ("tkinter", "tkagg"), - ("wx", "wxagg")]] + backends = [] + for deps, backend in [(["cairocffi", "pgi"], "gtk3agg"), + (["cairocffi", "pgi"], "gtk3cairo"), + (["PyQt5"], "qt5agg"), + (["tkinter"], "tkagg"), + (["wx"], "wxagg")]: + reason = None + if sys.version_info < (3,): + reason = "Py3-only test" + elif not os.environ.get("DISPLAY"): + reason = "No $DISPLAY" + elif any(importlib.util.find_spec(dep) is None for dep in deps): + reason = "Missing dependency" + backends.append(pytest.mark.skip(reason=reason)(backend) if reason + else backend) + return backends _test_script = """\