8000 Merge pull request #20990 from anntzer/tsr · matplotlib/matplotlib@c19c9fa · GitHub
[go: up one dir, main page]

Skip to content

Commit c19c9fa

Browse files
authored
Merge pull request #20990 from anntzer/tsr
Explicit registration of canvas-specific tool subclasses.
2 parents d37d732 + d480f24 commit c19c9fa

File tree

10 files changed

+90
-74
lines changed

10 files changed

+90
-74
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Support for passing tool names to ``ToolManager.add_tool``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
... has been removed. The second parameter to add_tool must now always be a
4+
tool class.

lib/matplotlib/_api/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,13 @@ def my_func(*args, **kwargs):
277277
raise
278278

279279

280+
def recursive_subclasses(cls):
281+
"""Yield *cls* and direct and indirect subclasses of *cls*."""
282+
yield cls
283+
for subcls in cls.__subclasses__():
284+
yield from recursive_subclasses(subcls)
285+
286+
280287
def warn_external(message, category=None):
281288
"""
282289
`warnings.warn` wrapper that sets *stacklevel* to "outside Matplotlib".

lib/matplotlib/backend_managers.py

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from matplotlib import _api, cbook, widgets
2-
import matplotlib.backend_tools as tools
1+
from matplotlib import _api, backend_tools, cbook, widgets
32

43

54
class ToolEvent:
@@ -233,8 +232,9 @@ def add_tool(self, name, tool, *args, **kwargs):
233232
----------
234233
name : str
235234
Name of the tool, treated as the ID, has to be unique.
236-
tool : class_like, i.e. str or type
237-
Reference to find the class of the Tool to added.
235+
tool : type
236+
Class of the tool to be added. A subclass will be used
237+
instead if one was registered for the current canvas class.
238238
239239
Notes
240240
-----
@@ -245,7 +245,7 @@ def add_tool(self, name, tool, *args, **kwargs):
245245
matplotlib.backend_tools.ToolBase : The base class for tools.
246246
"""
247247

248-
tool_cls = self._get_cls_to_instantiate(tool)
248+
tool_cls = backend_tools._find_tool_class(type(self.canvas), tool)
249249
if not tool_cls:
250250
raise ValueError('Impossible to find class for %s' % str(tool))
251251

@@ -254,7 +254,7 @@ def add_tool(self, name, tool, *args, **kwargs):
254254
'exists, not added')
255255
return self._tools[name]
256256

257-
if name == 'cursor' and tool_cls != tools.SetCursorBase:
257+
if name == 'cursor' and tool_cls != backend_tools.SetCursorBase:
258258
_api.warn_deprecated("3.5",
259259
message="Overriding ToolSetCursor with "
260260
f"{tool_cls.__qualname__} was only "
@@ -271,7 +271,7 @@ def add_tool(self, name, tool, *args, **kwargs):
271271
self.update_keymap(name, tool_cls.default_keymap)
272272

273273
# For toggle tools init the radio_group in self._toggled
274-
if isinstance(tool_obj, tools.ToolToggleBase):
274+
if isinstance(tool_obj, backend_tools.ToolToggleBase):
275275
# None group is not mutually exclusive, a set is used to keep track
276276
# of all toggled tools in this group
277277
if tool_obj.radio_group is None:
@@ -337,23 +337,6 @@ def _handle_toggle(self, tool, sender, canvasevent, data):
337337
F438 # Keep track of the toggled tool in the radio_group
338338
self._toggled[radio_group] = toggled
339339

340-
def _get_cls_to_instantiate(self, callback_class):
341-
# Find the class that corresponds to the tool
342-
if isinstance(callback_class, str):
343-
# FIXME: make more complete searching structure
344-
if callback_class in globals():
345-
callback_class = globals()[callback_class]
346-
else:
347-
mod = 'backend_tools'
348-
current_module = __import__(mod,
349-
globals(), locals(), [mod], 1)
350-
351-
callback_class = getattr(current_module, callback_class, False)
352-
if callable(callback_class):
353-
return callback_class
354-
else:
355-
return None
356-
357340
def trigger_tool(self, name, sender=None, canvasevent=None, data=None):
358341
"""
359342
Trigger a tool and emit the ``tool_trigger_{name}`` event.
@@ -376,7 +359,7 @@ def trigger_tool(self, name, sender=None, canvasevent=None, data=None):
376359
if sender is None:
377360
sender = self
378361

379-
if isinstance(tool, tools.ToolToggleBase):
362+
if isinstance(tool, backend_tools.ToolToggleBase):
380363
self._handle_toggle(tool, sender, canvasevent, data)
381364

382365
tool.trigger(sender, canvasevent, data) # Actually trigger Tool.
@@ -418,7 +401,8 @@ def get_tool(self, name, warn=True):
418401
`.ToolBase` or None
419402
The tool or None if no tool with the given name exists.
420403
"""
421-
if isinstance(name, tools.ToolBase) and name.name in self._tools:
404+
if (isinstance(name, backend_tools.ToolBase)
405+
and name.name in self._tools):
422406
return name
423407
if name not in self._tools:
424408
if warn:
10000

lib/matplotlib/backend_tools.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"""
1313

1414
import enum
15+
import functools
1516
import re
1617
import time
1718
from types import SimpleNamespace
@@ -36,6 +37,35 @@ class Cursors(enum.IntEnum): # Must subclass int for the macOS backend.
3637
RESIZE_VERTICAL = enum.auto()
3738
cursors = Cursors # Backcompat.
3839

40+
41+
# _tool_registry, _register_tool_class, and _find_tool_class implement a
42+
# mechanism through which ToolManager.add_tool can determine whether a subclass
43+
# of the requested tool class has been registered (either for the current
44+
# canvas class or for a parent class), in which case that tool subclass will be
45+
# instantiated instead. This is the mechanism used e.g. to allow different
46+
# GUI backends to implement different specializations for ConfigureSubplots.
47+
48+
49+
_tool_registry = set()
50+
51+
52+
def _register_tool_class(canvas_cls, tool_cls=None):
53+
"""Decorator registering *tool_cls* as a tool class for *canvas_cls*."""
54+
if tool_cls is None:
55+
return functools.partial(_register_tool_class, canvas_cls)
56+
_tool_registry.add((canvas_cls, tool_cls))
57+
return tool_cls
58+
59+
60+
def _find_tool_class(canvas_cls, tool_cls):
61+
"""Find a subclass of *tool_cls* registered for *canvas_cls*."""
62+
for canvas_parent in canvas_cls.__mro__:
63+
for tool_child in _api.recursive_subclasses(tool_cls):
64+
if (canvas_parent, tool_child) in _tool_registry:
65+
return tool_child
66+
return tool_cls
67+
68+
3969
# Views positions tool
4070
_views_positions = 'viewpos'
4171

@@ -943,8 +973,8 @@ def trigger(self, *args, **kwargs):
943973

944974
default_tools = {'home': ToolHome, 'back': ToolBack, 'forward': ToolForward,
945975
'zoom': ToolZoom, 'pan': ToolPan,
946-
'subplots': 'ToolConfigureSubplots',
947-
'save': 'ToolSaveFigure',
976+
'subplots': ConfigureSubplotsBase,
977+
'save': SaveFigureBase,
948978
'grid': ToolGrid,
949979
'grid_minor': ToolMinorGrid,
950980
'fullscreen': ToolFullScreen,
@@ -954,10 +984,10 @@ def trigger(self, *args, **kwargs):
954984
'yscale': ToolYScale,
955985
'position': ToolCursorPosition,
956986
_views_positions: ToolViewsPositions,
957-
'cursor': 'ToolSetCursor',
958-
'rubberband': 'ToolRubberband',
959-
'help': 'ToolHelp',
960-
'copy': 'ToolCopyToClipboard',
987+
'cursor': SetCursorBase,
988+
'rubberband': RubberbandBase,
989+
'help': ToolHelpBase,
990+
'copy': ToolCopyToClipboardBase,
961991
}
962992
"""Default tools"""
963993

lib/matplotlib/backends/_backend_tk.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,7 @@ def hidetip(self):
774774
tw.destroy()
775775

776776

777+
@backend_tools._register_tool_class(FigureCanvasTk)
777778
class RubberbandTk(backend_tools.RubberbandBase):
778779
def draw_rubberband(self, x0, y0, x1, y1):
779780
self.remove_rubberband()
@@ -859,12 +860,14 @@ def set_message(self, s):
859860
self._message.set(s)
860861

861862

863+
@backend_tools._register_tool_class(FigureCanvasTk)
862864
class SaveFigureTk(backend_tools.SaveFigureBase):
863865
def trigger(self, *args):
864866
NavigationToolbar2Tk.save_figure(
865867
self._make_classic_style_pseudo_toolbar())
866868

867869

870+
@backend_tools._register_tool_class(FigureCanvasTk)
868871
class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase):
869872
def __init__(self, *args, **kwargs):
870873
super().__init__(*args, **kwargs)
@@ -894,18 +897,14 @@ def destroy(self, *args, **kwargs):
894897
self.window = None
895898

896899

900+
@backend_tools._register_tool_class(FigureCanvasTk)
897901
class HelpTk(backend_tools.ToolHelpBase):
898902
def trigger(self, *args):
899903
dialog = SimpleDialog(
900904
self.figure.canvas._tkcanvas, self._get_help_text(), ["OK"])
901905
dialog.done = lambda num: dialog.frame.master.withdraw()
902906

903907

904-
backend_tools.ToolSaveFigure = SaveFigureTk
905-
backend_tools.ToolConfigureSubplots = ConfigureSubplotsTk
906-
backend_tools.ToolRubberband = RubberbandTk
907-
backend_tools.ToolHelp = HelpTk
908-
backend_tools.ToolCopyToClipboard = backend_tools.ToolCopyToClipboardBase
909908
Toolbar = ToolbarTk
910909

911910

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@
2929
raise ImportError from e
3030

3131
from gi.repository import Gio, GLib, GObject, Gtk, Gdk
32+
from . import _backend_gtk
3233
from ._backend_gtk import (
3334
_create_application, _shutdown_application,
3435
backend_version, _BackendGTK, _NavigationToolbar2GTK,
3536
TimerGTK as TimerGTK3,
36-
ConfigureSubplotsGTK as ConfigureSubplotsGTK3,
37-
RubberbandGTK as RubberbandGTK3,
3837
)
3938

4039

@@ -597,6 +596,7 @@ def set_message(self, s):
597596
self._message.set_label(s)
598597

599598

599+
@backend_tools._register_tool_class(FigureCanvasGTK3)
600600
class SaveFigureGTK3(backend_tools.SaveFigureBase):
601601
def trigger(self, *args, **kwargs):
602602

@@ -613,6 +613,7 @@ def set_cursor(self, cursor):
613613
self._make_classic_style_pseudo_toolbar(), cursor)
614614

615615

616+
@backend_tools._register_tool_class(FigureCanvasGTK3)
616617
class HelpGTK3(backend_tools.ToolHelpBase):
617618
def _normalize_shortcut(self, key):
618619
"""
@@ -698,6 +699,7 @@ def trigger(self, *args):
698699
self._show_shortcuts_dialog()
699700

700701

702+
@backend_tools._register_tool_class(FigureCanvasGTK3)
701703
class ToolCopyToClipboardGTK3(backend_tools.ToolCopyToClipboardBase):
702704
def trigger(self, *args, **kwargs):
703705
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
@@ -721,13 +723,11 @@ def error_msg_gtk(msg, parent=None):
721723
dialog.destroy()
722724

723725

724-
backend_tools.ToolSaveFigure = SaveFigureGTK3
725-
backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3
726-
backend_tools.ToolRubberband = RubberbandGTK3
727-
backend_tools.ToolHelp = HelpGTK3
728-
backend_tools.ToolCopyToClipboard = ToolCopyToClipboardGTK3
729-
730726
Toolbar = ToolbarGTK3
727+
backend_tools._register_tool_class(
728+
FigureCanvasGTK3, _backend_gtk.ConfigureSubplotsGTK)
729+
backend_tools._register_tool_class(
730+
FigureCanvasGTK3, _backend_gtk.RubberbandGTK)
731731

732732

733733
@_Backend.export

lib/matplotlib/backends/backend_gtk4.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@
2929
raise ImportError from e
3030

3131
from gi.repository import Gio, GLib, GObject, Gtk, Gdk, GdkPixbuf
32+
from . import _backend_gtk
3233
from ._backend_gtk import (
3334
_create_application, _shutdown_application,
3435
backend_version, _BackendGTK, _NavigationToolbar2GTK,
3536
TimerGTK as TimerGTK4,
36-
ConfigureSubplotsGTK as ConfigureSubplotsGTK4,
37-
RubberbandGTK as RubberbandGTK4,
3837
)
3938

4039

@@ -564,6 +563,7 @@ def set_message(self, s):
564563
self._message.set_label(s)
565564

566565

566+
@backend_tools._register_tool_class(FigureCanvasGTK4)
567567
class SaveFigureGTK4(backend_tools.SaveFigureBase):
568568
def trigger(self, *args, **kwargs):
569569

@@ -573,6 +573,7 @@ class PseudoToolbar:
573573
return NavigationToolbar2GTK4.save_figure(PseudoToolbar())
574574

575575

576+
@backend_tools._register_tool_class(FigureCanvasGTK4)
576577
class HelpGTK4(backend_tools.ToolHelpBase):
577578
def _normalize_shortcut(self, key):
578579
"""
@@ -646,6 +647,7 @@ def trigger(self, *args):
646647
window.show()
647648

648649

650+
@backend_tools._register_tool_class(FigureCanvasGTK4)
649651
class ToolCopyToClipboardGTK4(backend_tools.ToolCopyToClipboardBase):
650652
def trigger(self, *args, **kwargs):
651653
with io.BytesIO() as f:
@@ -658,12 +660,10 @@ def trigger(self, *args, **kwargs):
658660
clipboard.set(pb)
659661

660662

661-
backend_tools.ToolSaveFigure = SaveFigureGTK4
662-
backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK4
663-
backend_tools.ToolRubberband = RubberbandGTK4
664-
backend_tools.ToolHelp = HelpGTK4
665-
backend_tools.ToolCopyToClipboard = ToolCopyToClipboardGTK4
666-
663+
backend_tools._register_tool_class(
664+
FigureCanvasGTK4, _backend_gtk.ConfigureSubplotsGTK)
665+
backend_tools._register_tool_class(
666+
FigureCanvasGTK4, _backend_gtk.RubberbandGTK)
667667
Toolbar = ToolbarGTK4
668668

669669

lib/matplotlib/backends/backend_qt.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -952,12 +952,14 @@ def set_message(self, s):
952952
self.widgetForAction(self._message_action).setText(s)
953953

954954

955+
@backend_tools._register_tool_class(FigureCanvasQT)
955956
class ConfigureSubplotsQt(backend_tools.ConfigureSubplotsBase):
956957
def trigger(self, *args):
957958
NavigationToolbar2QT.configure_subplots(
958959
self._make_classic_style_pseudo_toolbar())
959960

960961

962+
@backend_tools._register_tool_class(FigureCanvasQT)
961963
class SaveFigureQt(backend_tools.SaveFigureBase):
962964
def trigger(self, *args):
963965
NavigationToolbar2QT.save_figure(
@@ -971,6 +973,7 @@ def set_cursor(self, cursor):
971973
self._make_classic_style_pseudo_toolbar(), cursor)
972974

973975

976+
@backend_tools._register_tool_class(FigureCanvasQT)
974977
class RubberbandQt(backend_tools.RubberbandBase):
975978
def draw_rubberband(self, x0, y0, x1, y1):
976979
NavigationToolbar2QT.draw_rubberband(
@@ -981,24 +984,19 @@ def remove_rubberband(self):
981984
self._make_classic_style_pseudo_toolbar())
982985

983986

987+
@backend_tools._register_tool_class(FigureCanvasQT)
984988
class HelpQt(backend_tools.ToolHelpBase):
985989
def trigger(self, *args):
986990
QtWidgets.QMessageBox.information(None, "Help", self._get_help_html())
987991

988992

993+
@backend_tools._register_tool_class(FigureCanvasQT)
989994
class ToolCopyToClipboardQT(backend_tools.ToolCopyToClipboardBase):
990995
def trigger(self, *args, **kwargs):
991996
pixmap = self.canvas.grab()
992997
qApp.clipboard().setPixmap(pixmap)
993998

994999

995-
backend_tools.ToolSaveFigure = SaveFigureQt
996-
backend_tools.ToolConfigureSubplots = ConfigureSubplotsQt
997-
backend_tools.ToolRubberband = RubberbandQt
998-
backend_tools.ToolHelp = HelpQt
999-
backend_tools.ToolCopyToClipboard = ToolCopyToClipboardQT
1000-
1001-
10021000
@_Backend.export
10031001
class _BackendQT(_Backend):
10041002
FigureCanvas = FigureCanvasQT

0 commit comments

Comments
 (0)
0