From 675f48fc6094295f8e8c3cde49a1a929e9195030 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 10 Jun 2019 18:52:11 +0200 Subject: [PATCH] Move required_interactive_framework to canvas class. In mpl 3.0, the required_interactive_framework attribute was added to backend modules. However, it is sometimes required to access it from a canvas *instance* (e.g. in _fix_ipython_backend2gui), but the pattern `sys.modules[cls.__module__].required_interactive_framework` is brittle (e.g. it is broken by third-party subclasses). Instead it makes more sense to put the attribute on the canvas class; one can easily and robustly go from the backend module to the canvas class by accessing the FigureCanvas attribute. --- doc/api/next_api_changes/2019-06-12-AL.rst | 11 ++++++++ lib/matplotlib/backend_bases.py | 30 ++++++++++----------- lib/matplotlib/backends/_backend_tk.py | 3 ++- lib/matplotlib/backends/backend_gtk3.py | 3 ++- lib/matplotlib/backends/backend_macosx.py | 4 +-- lib/matplotlib/backends/backend_qt4.py | 9 ++++--- lib/matplotlib/backends/backend_qt4agg.py | 3 ++- lib/matplotlib/backends/backend_qt4cairo.py | 5 ++-- lib/matplotlib/backends/backend_qt5.py | 2 +- lib/matplotlib/backends/backend_wx.py | 3 ++- lib/matplotlib/pyplot.py | 3 ++- 11 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 doc/api/next_api_changes/2019-06-12-AL.rst diff --git a/doc/api/next_api_changes/2019-06-12-AL.rst b/doc/api/next_api_changes/2019-06-12-AL.rst new file mode 100644 index 000000000000..5e0cd52a3d72 --- /dev/null +++ b/doc/api/next_api_changes/2019-06-12-AL.rst @@ -0,0 +1,11 @@ +API changes +``````````` + +The ``required_interactive_framework`` attribute of backend modules introduced +in Matplotlib 3.0 has been moved to the FigureCanvas class, in order to let it +be inherited by third-party canvas subclasses and to make it easier to know +what interactive framework is required by a canvas class. + +``backend_qt4.FigureCanvasQT5``, which is an alias for +``backend_qt5.FigureCanvasQT`` (but only exists under that name in +``backend_qt4``), has been removed. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 8100d75b64dd..14ecfb45e0a1 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1560,8 +1560,12 @@ class FigureCanvasBase: ---------- figure : `matplotlib.figure.Figure` A high-level figure instance - """ + + # Set to one of {"qt5", "qt4", "gtk3", "wx", "tk", "macosx"} if an + # interactive framework is required, or None otherwise. + required_interactive_framework = None + events = [ 'resize_event', 'draw_event', @@ -1633,8 +1637,7 @@ def _fix_ipython_backend2gui(cls): # In case we ever move the patch to IPython and remove these APIs, # don't break on our side. return - backend_mod = sys.modules[cls.__module__] - rif = getattr(backend_mod, "required_interactive_framework", None) + rif = getattr(cls, "required_interactive_framework", None) backend2gui_rif = {"qt5": "qt", "qt4": "qt", "gtk3": "gtk3", "wx": "wx", "macosx": "osx"}.get(rif) if backend2gui_rif: @@ -3255,10 +3258,6 @@ class _Backend: # class FooBackend(_Backend): # # override the attributes and methods documented below. - # Set to one of {"qt5", "qt4", "gtk3", "wx", "tk", "macosx"} if an - # interactive framework is required, or None otherwise. - required_interactive_framework = None - # `backend_version` may be overridden by the subclass. backend_version = "unknown" @@ -3341,14 +3340,15 @@ def show(cls, block=None): @staticmethod def export(cls): - for name in ["required_interactive_framework", - "backend_version", - "FigureCanvas", - "FigureManager", - "new_figure_manager", - "new_figure_manager_given_figure", - "draw_if_interactive", - "show"]: + for name in [ + "backend_version", + "FigureCanvas", + "FigureManager", + "new_figure_manager", + "new_figure_manager_given_figure", + "draw_if_interactive", + "show", + ]: setattr(sys.modules[cls.__module__], name, getattr(cls, name)) # For back-compatibility, generate a shim `Show` class. diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index b388a0cd6989..f0b8085dfa9a 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -124,6 +124,8 @@ def _on_timer(self): class FigureCanvasTk(FigureCanvasBase): + required_interactive_framework = "tk" + keyvald = {65507: 'control', 65505: 'shift', 65513: 'alt', @@ -868,7 +870,6 @@ def trigger(self, *args): @_Backend.export class _BackendTk(_Backend): - required_interactive_framework = "tk" FigureManager = FigureManagerTk @classmethod diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 4fb63b17cbb5..9788fd9e3e83 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -97,6 +97,8 @@ def _on_timer(self): class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase): + required_interactive_framework = "gtk3" + keyvald = {65507: 'control', 65505: 'shift', 65513: 'alt', @@ -978,7 +980,6 @@ def error_msg_gtk(msg, parent=None): @_Backend.export class _BackendGTK3(_Backend): - required_interactive_framework = "gtk3" FigureCanvas = FigureCanvasGTK3 FigureManager = FigureManagerGTK3 diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index c90404532ec3..7118d7f52c9f 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -53,9 +53,10 @@ class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasAgg): ---------- figure : `matplotlib.figure.Figure` A high-level Figure instance - """ + required_interactive_framework = "macosx" + def __init__(self, figure): FigureCanvasBase.__init__(self, figure) width, height = self.get_width_height() @@ -172,7 +173,6 @@ def set_message(self, message): @_Backend.export class _BackendMac(_Backend): - required_interactive_framework = "macosx" FigureCanvas = FigureCanvasMac FigureManager = FigureManagerMac diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index f51066f7d57d..e74c92be20b6 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -1,10 +1,11 @@ from .backend_qt5 import ( backend_version, SPECIAL_KEYS, SUPER, ALT, CTRL, SHIFT, MODIFIER_KEYS, - cursord, _create_qApp, _BackendQT5, TimerQT, MainWindow, FigureManagerQT, - NavigationToolbar2QT, SubplotToolQt, error_msg_qt, exception_handler) -from .backend_qt5 import FigureCanvasQT as FigureCanvasQT5 + cursord, _create_qApp, _BackendQT5, TimerQT, MainWindow, FigureCanvasQT, + FigureManagerQT, NavigationToolbar2QT, SubplotToolQt, error_msg_qt, + exception_handler) @_BackendQT5.export class _BackendQT4(_BackendQT5): - required_interactive_framework = "qt4" + class FigureCanvas(FigureCanvasQT): + required_interactive_framework = "qt4" diff --git a/lib/matplotlib/backends/backend_qt4agg.py b/lib/matplotlib/backends/backend_qt4agg.py index 8f4e265ac424..03e7ac920566 100644 --- a/lib/matplotlib/backends/backend_qt4agg.py +++ b/lib/matplotlib/backends/backend_qt4agg.py @@ -8,4 +8,5 @@ @_BackendQT5Agg.export class _BackendQT4Agg(_BackendQT5Agg): - required_interactive_framework = "qt4" + class FigureCanvas(FigureCanvasQTAgg): + required_interactive_framework = "qt4" diff --git a/lib/matplotlib/backends/backend_qt4cairo.py b/lib/matplotlib/backends/backend_qt4cairo.py index 94d703afa4ea..f785aa0bd40b 100644 --- a/lib/matplotlib/backends/backend_qt4cairo.py +++ b/lib/matplotlib/backends/backend_qt4cairo.py @@ -1,6 +1,7 @@ -from .backend_qt5cairo import _BackendQT5Cairo +from .backend_qt5cairo import _BackendQT5Cairo, FigureCanvasQTCairo @_BackendQT5Cairo.export class _BackendQT4Cairo(_BackendQT5Cairo): - required_interactive_framework = "qt4" + class FigureCanvas(FigureCanvasQTCairo): + required_interactive_framework = "qt4" diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index c3837f2fa069..1fb3f0d8ac53 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -211,6 +211,7 @@ def _timer_stop(self): class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase): + required_interactive_framework = "qt5" # map Qt button codes to MouseEvent's ones: buttond = {QtCore.Qt.LeftButton: MouseButton.LEFT, @@ -1029,7 +1030,6 @@ def trigger(self, *args, **kwargs): @_Backend.export class _BackendQT5(_Backend): - required_interactive_framework = "qt5" FigureCanvas = FigureCanvasQT FigureManager = FigureManagerQT diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 732abb5ed58e..11ec680a5708 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -519,6 +519,8 @@ class _FigureCanvasWxBase(FigureCanvasBase, wx.Panel): we give a hint as to our preferred minimum size. """ + required_interactive_framework = "wx" + keyvald = { wx.WXK_CONTROL: 'control', wx.WXK_SHIFT: 'shift', @@ -1933,7 +1935,6 @@ def OnPrintPage(self, page): @_Backend.export class _BackendWx(_Backend): - required_interactive_framework = "wx" FigureCanvas = FigureCanvasWx FigureManager = FigureManagerWx _frame_class = FigureFrameWx diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 78afeeb5ec19..fd202458036a 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -224,7 +224,8 @@ def switch_backend(newbackend): _log.debug("Loaded backend %s version %s.", newbackend, Backend.backend_version) - required_framework = Backend.required_interactive_framework + required_framework = getattr( + Backend.FigureCanvas, "required_interactive_framework", None) if required_framework is not None: current_framework = \ matplotlib.backends._get_running_interactive_framework()