diff --git a/.travis.yml b/.travis.yml index 9342444f8fca..3ad28f824cb2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,9 @@ addons: - graphviz - libgeos-dev - otf-freefont + - libpangocairo-1.0-0 + - libgirepository-1.0-1 + - gir1.2-gtk-3.0 # - fonts-humor-sans # sources: # - debian-sid @@ -112,7 +115,7 @@ install: pip install --upgrade setuptools - | # Install dependencies from pypi - pip install $PRE python-dateutil $NUMPY pyparsing!=2.1.6 $PANDAS pep8 cycler coveralls coverage + pip install $PRE python-dateutil $NUMPY pyparsing!=2.1.6 $PANDAS pep8 cycler coveralls coverage pgi cairocffi pip install $PRE -r doc-requirements.txt # Install nose from a build which has partial @@ -160,7 +163,7 @@ script: if [[ $TRAVIS_OS_NAME == 'osx' ]]; then python tests.py $NOSE_ARGS $TEST_ARGS else - gdb -return-child-result -batch -ex r -ex bt --args python $PYTHON_ARGS tests.py $NOSE_ARGS $TEST_ARGS + xvfb-run gdb -return-child-result -batch -ex r -ex bt --args python $PYTHON_ARGS tests.py $NOSE_ARGS $TEST_ARGS fi else echo The following args are passed to pytest $PYTEST_ARGS diff --git a/INSTALL b/INSTALL index aade12aab122..0fabeb3c5ccf 100644 --- a/INSTALL +++ b/INSTALL @@ -235,6 +235,10 @@ backends and the capabilities they provide. :term:`pyqt` 4.4 or later The Qt4 widgets library python wrappers for the Qt4Agg backend +:term:`PyGObject` or `pgi` + For Gtk3, MPL requires the installation of a GObject introspection library + for python, either `PyGObject` (also known as gi) or `pgi`. + :term:`pygtk` 2.4 or later The python wrappers for the GTK widgets library for use with the GTK or GTKAgg backend diff --git a/doc/glossary/index.rst b/doc/glossary/index.rst index 5f0b683f14cf..ac77926b4952 100644 --- a/doc/glossary/index.rst +++ b/doc/glossary/index.rst @@ -64,6 +64,17 @@ 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. + + 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. + pygtk `pygtk `_ provides python wrappers for the :term:`GTK` widgets library for use with the GTK or GTKAgg diff --git a/doc/users/whats_new/2015-07-30_pgi.rst b/doc/users/whats_new/2015-07-30_pgi.rst new file mode 100644 index 000000000000..08d303d84280 --- /dev/null +++ b/doc/users/whats_new/2015-07-30_pgi.rst @@ -0,0 +1,9 @@ +PGI - Pure Python GObject Introspection Bindings +------------------------------------------------ + +For the GTK3 backend, matplotlib now supports PGI bindings as an alternative +to PyGObject. By default matplotlib will still use PyGObject, otherwise it +will look for pgi. You can change this behaviour through the rcParam +backend.gi_preference which takes either a string, or a list of strings in +order of preference. + diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 16969e5545dd..a52616331a51 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1496,6 +1496,7 @@ def _jupyter_nbextension_paths(): 'matplotlib.tests.test_backend_ps', 'matplotlib.tests.test_backend_qt4', 'matplotlib.tests.test_backend_qt5', + 'matplotlib.tests.test_backend_gtk3', 'matplotlib.tests.test_backend_svg', 'matplotlib.tests.test_basic', 'matplotlib.tests.test_bbox_tight', diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index c567a5a2c34c..3e5ef9611cd4 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -29,25 +29,7 @@ def _fn_name(): return sys._getframe(1).f_code.co_name -try: - import cairocffi as cairo -except ImportError: - try: - import cairo - except ImportError: - raise ImportError("Cairo backend requires that cairocffi or pycairo is installed.") - else: - HAS_CAIRO_CFFI = False -else: - HAS_CAIRO_CFFI = True - -_version_required = (1,2,0) -if cairo.version_info < _version_required: - raise ImportError ("Pycairo %d.%d.%d is installed\n" - "Pycairo %d.%d.%d or later is required" - % (cairo.version_info + _version_required)) -backend_version = cairo.version -del _version_required +from .cairo_compat import cairo, HAS_CAIRO_CFFI from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\ FigureManagerBase, FigureCanvasBase diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 1aee5c75f590..870b239278a5 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -6,25 +6,7 @@ import os, sys def fn_name(): return sys._getframe(1).f_code.co_name -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.") +from .gtk3_compat import Gtk, Gdk, GObject, GLib import matplotlib from matplotlib._pylab_helpers import Gcf @@ -167,6 +149,12 @@ class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): 65421 : 'enter', } + modifier_keys = [ + (Gdk.ModifierType.MOD4_MASK, 'super'), + (Gdk.ModifierType.MOD1_MASK, 'alt'), + (Gdk.ModifierType.CONTROL_MASK, 'ctrl'), + ] + # Setting this as a static constant prevents # this resulting expression from leaking event_mask = (Gdk.EventMask.BUTTON_PRESS_MASK | @@ -293,12 +281,7 @@ def _get_key(self, event): else: key = None - modifiers = [ - (Gdk.ModifierType.MOD4_MASK, 'super'), - (Gdk.ModifierType.MOD1_MASK, 'alt'), - (Gdk.ModifierType.CONTROL_MASK, 'ctrl'), - ] - for key_mask, prefix in modifiers: + for key_mask, prefix in self.modifier_keys: if event.state & key_mask: key = '{0}+{1}'.format(prefix, key) diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index c3eb1da68be3..e8cbb5373257 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -9,7 +9,7 @@ from . import backend_agg from . import backend_gtk3 -from .backend_cairo import cairo, HAS_CAIRO_CFFI +from .cairo_compat import cairo, HAS_CAIRO_CFFI from matplotlib.figure import Figure from matplotlib import transforms @@ -46,7 +46,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 da8f099be7f6..fe5d2d6192bf 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -5,12 +5,12 @@ from . import backend_gtk3 from . import backend_cairo -from .backend_cairo import cairo, HAS_CAIRO_CFFI +from .cairo_compat import cairo, HAS_CAIRO_CFFI from matplotlib.figure import Figure 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/backends/cairo_compat.py b/lib/matplotlib/backends/cairo_compat.py new file mode 100644 index 000000000000..0d6215beaaf7 --- /dev/null +++ b/lib/matplotlib/backends/cairo_compat.py @@ -0,0 +1,20 @@ +try: + import cairocffi as cairo +except ImportError: + try: + import cairo + except ImportError: + raise ImportError( + "Cairo backend requires that cairocffi or pycairo is installed.") + else: + HAS_CAIRO_CFFI = False +else: + HAS_CAIRO_CFFI = True + +_version_required = (1, 2, 0) +if cairo.version_info < _version_required: + raise ImportError("Pycairo %d.%d.%d is installed\n" + "Pycairo %d.%d.%d or later is required" + % (cairo.version_info + _version_required)) +backend_version = cairo.version +del _version_required diff --git a/lib/matplotlib/backends/gtk3_compat.py b/lib/matplotlib/backends/gtk3_compat.py new file mode 100644 index 000000000000..c3847041d4f6 --- /dev/null +++ b/lib/matplotlib/backends/gtk3_compat.py @@ -0,0 +1,34 @@ +import matplotlib + +error_msg_gtk3 = "Gtk3 backend requires the installation of pygobject or pgi." + +# Import the first library that works from the rcParam list +# throw ImportError if none works +for lib in matplotlib.rcParams['backend.gi_preference']: + try: + gi = __import__(lib, globals(), locals(), [], 0) + break + except ImportError: + pass +else: + raise ImportError(error_msg_gtk3) + +# Check version +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 installation of GObject introspection " + "bindings for Gtk 3") + +# cleanly import pkgs to global scope +try: + pkgs = ['Gtk', 'Gdk', 'GObject', 'GLib'] + name = gi.__name__ + '.repository' + _temp = __import__(name, globals(), locals(), pkgs, 0) + globals().update(dict((k, getattr(_temp, k)) for k in pkgs)) +except (ImportError, AttributeError): + raise ImportError(error_msg_gtk3) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index fcbd7adfe313..8a0c81eb406a 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -410,6 +410,9 @@ def deprecate_axes_colorcycle(value): validate_stringlist = _listify_validator(six.text_type) validate_stringlist.__doc__ = 'return a list' +validate_gi_preference = _listify_validator(ValidateInStrings( + 'backend.gi_preference', ['gi', 'pgi'])) + validate_orientation = ValidateInStrings( 'orientation', ['landscape', 'portrait']) @@ -888,6 +891,7 @@ def validate_animation_writer_path(p): 'backend_fallback': [True, validate_bool], # agg is certainly present 'backend.qt4': ['PyQt4', validate_qt4], 'backend.qt5': ['PyQt5', validate_qt5], + 'backend.gi_preference': [['gi', 'pgi'], validate_gi_preference], 'webagg.port': [8988, validate_int], 'webagg.open_in_browser': [True, validate_bool], 'webagg.port_retries': [50, validate_int], diff --git a/lib/matplotlib/tests/test_backend_gtk3.py b/lib/matplotlib/tests/test_backend_gtk3.py new file mode 100644 index 000000000000..47953432f7e6 --- /dev/null +++ b/lib/matplotlib/tests/test_backend_gtk3.py @@ -0,0 +1,60 @@ +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +from matplotlib.externals import six +from matplotlib.externals.six import unichr +from matplotlib import pyplot as plt +from matplotlib.testing.decorators import cleanup, switch_backend +from matplotlib.testing.decorators import knownfailureif +from matplotlib._pylab_helpers import Gcf +import copy + +try: + # mock in python 3.3+ + from unittest import mock +except ImportError: + import mock + +try: + from matplotlib.backends.gtk3_compat import Gtk, Gdk, GObject, GLib + HAS_GTK3 = True +except ImportError: + HAS_GTK3 = False + + +def simulate_key_press(canvas, key, modifiers=[]): + event = mock.Mock() + + keyval = [k for k, v in six.iteritems(canvas.keyvald) if v == key] + if keyval: + keyval = keyval[0] + else: + keyval = ord(key) + event.keyval = keyval + + event.state = 0 + for key_mask, prefix in canvas.modifier_keys: + if prefix in modifiers: + event.state |= key_mask + + canvas.key_press_event(None, event) + + +@cleanup +#@knownfailureif(not HAS_GTK3) +@switch_backend('GTK3Agg') +def test_fig_close(): + #save the state of Gcf.figs + init_figs = copy.copy(Gcf.figs) + + # make a figure using pyplot interface + fig = plt.figure() + + # simulate user pressing the close shortcut + #simulate_key_press(fig.canvas, 'w', ['ctrl']) + + plt.show() + + # assert that we have removed the reference to the FigureManager + # that got added by plt.figure() + assert(init_figs == Gcf.figs) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy