8000 Merge pull request #27492 from anntzer/mep22imagename · matplotlib/matplotlib@fef38dd · GitHub
[go: up one dir, main page]

Skip to content

Commit fef38dd

Browse files
authored
Merge pull request #27492 from anntzer/mep22imagename
Fix semantics of MEP22 image names.
2 parents 787f308 + 8470189 commit fef38dd

File tree

3 files changed

+65
-24
lines changed

3 files changed

+65
-24
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Image path semantics of toolmanager-based tools
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Previously, MEP22 ("toolmanager-based") Tools would try to load their icon
4+
(``tool.image``) relative to the current working directory, or, as a fallback,
5+
from Matplotlib's own image directory. Because both approaches are problematic
6+
for third-party tools (the end-user may change the current working directory
7+
at any time, and third-parties cannot add new icons in Matplotlib's image
8+
directory), this behavior is deprecated; instead, ``tool.image`` is now
9+
interpreted relative to the directory containing the source file where the
10+
``Tool.image`` class attribute is defined. (Defining ``tool.image`` as an
11+
absolute path also works and is compatible with both the old and the new
12+
semantics.)

lib/matplotlib/backend_bases.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import itertools
3636
import logging
3737
import os
38+
import pathlib
3839
import signal
3940
import socket
4041
import sys
@@ -3267,7 +3268,7 @@ def add_tool(self, tool, group, position=-1):
32673268
The position within the group to place this tool.
32683269
"""
32693270
tool = self.toolmanager.get_tool(tool)
3270-
image = self._get_image_filename(tool.image)
3271+
image = self._get_image_filename(tool)
32713272
toggle = getattr(tool, 'toggled', None) is not None
32723273
self.add_toolitem(tool.name, group, position,
32733274
image, tool.description, toggle)
@@ -3278,20 +3279,44 @@ def add_tool(self, tool, group, position=-1):
32783279
if tool.toggled:
32793280
self.toggle_toolitem(tool.name, True)
32803281

3281-
def _get_image_filename(self, image):
3282-
"""Find the image based on its name."""
3283-
if not image:
3282+
def _get_image_filename(self, tool):
3283+
"""Resolve a tool icon's filename."""
3284+
if not tool.image:
32843285
return None
3285-
3286-
basedir = cbook._get_data_path("images")
3287-
for fname in [
3288-
image,
3289-
image + self._icon_extension,
3290-
str(basedir / image),
3291-
str(basedir / (image + self._icon_extension)),
3286+
if os.path.isabs(tool.image):
3287+
filename = tool.image
3288+
else:
3289+
if "image" in getattr(tool, "__dict__", {}):
3290+
raise ValueError("If 'tool.image' is an instance variable, "
3291+
"it must be an absolute path")
3292+
for cls in type(tool).__mro__:
3293+
if "image" in vars(cls):
3294+
try:
3295+
src = inspect.getfile(cls)
3296+
break
3297+
except (OSError, TypeError):
3298+
raise ValueError("Failed to locate source file "
3299+
"where 'tool.image' is defined") from None
3300+
else:
3301+
raise ValueError("Failed to find parent class defining 'tool.image'")
3302+
filename = str(pathlib.Path(src).parent / tool.image)
3303+
for filename in [filename, filename + self._icon_extension]:
3304+
if os.path.isfile(filename):
3305+
return os.path.abspath(filename)
3306+
for fname in [ # Fallback; once deprecation elapses.
3307+
tool.image,
3308+
tool.image + self._icon_extension,
3309+
cbook._get_data_path("images", tool.image),
3310+
cbook._get_data_path("images", tool.image + self._icon_extension),
32923311
]:
32933312
if os.path.isfile(fname):
3294-
return fname
3313+
_api.warn_deprecated(
3314+
"3.9", message=f"Loading icon {tool.image!r} from the current "
3315+
"directory or from Matplotlib's image directory. This behavior "
3316+
"is deprecated since %(since)s and will be removed %(removal)s; "
3317+
"Tool.image should be set to a path relative to the Tool's source "
3318+
"file, or to an absolute path.")
3319+
return os.path.abspath(fname)
32953320

32963321
def trigger_tool(self, name):
32973322
"""

lib/matplotlib/backend_tools.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,14 @@ class ToolBase:
9797

9898
image = None
9999
"""
100-
Filename of the image.
101-
102-
`str`: Filename of the image to use in a Toolbar. If None, the *name* is
103-
used as a label in the toolbar button.
100+
Icon filename.
101+
102+
``str | None``: Filename of the Toolbar icon; either absolute, or
103+
relative to the directory containing the Python source file where the
104+
``Tool.image``class attribute is defined (in the latter case, this cannot
105+
be defined as an instance attribute). In either case, the extension is
106+
optional; leaving it off lets individual backends select the icon format
107+
they prefer. If None, the *name* is used as a label in the toolbar button.
104108
"""
105109

106110
def __init__(self, toolmanager, name):
@@ -601,7 +605,7 @@ class ToolHome(ViewsPositionsBase):
601605
"""Restore the original view limits."""
602606

603607
description = 'Reset original view'
604-
image = 'home'
608+
image = 'mpl-data/images/home'
605609
default_keymap = property(lambda self: mpl.rcParams['keymap.home'])
606610
_on_trigger = 'home'
607611

@@ -610,7 +614,7 @@ class ToolBack(ViewsPositionsBase):
610614
"""Move back up the view limits stack."""
611615

612616
description = 'Back to previous view'
613-
image = 'back'
617+
image = 'mpl-data/images/back'
614618
default_keymap = property(lambda self: mpl.rcParams['keymap.back'])
615619
_on_trigger = 'back'
616620

@@ -619,7 +623,7 @@ class ToolForward(ViewsPositionsBase):
619623
"""Move forward in the view lim stack."""
620624

621625
description = 'Forward to next view'
622-
image = 'forward'
626+
image = 'mpl-data/images/forward'
623627
default_keymap = property(lambda self: mpl.rcParams['keymap.forward'])
624628
_on_trigger = 'forward'
625629

@@ -628,14 +632,14 @@ class ConfigureSubplotsBase(ToolBase):
628632
"""Base tool for the configuration of subplots."""
629633

630634
description = 'Configure subplots'
631-
image = 'subplots'
635+
image = 'mpl-data/images/subplots'
632636

633637

634638
class SaveFigureBase(ToolBase):
635639
"""Base tool for figure saving."""
636640

637641
description = 'Save the figure'
638-
image = 'filesave'
642+
image = 'mpl-data/images/filesave'
639643
default_keymap = property(lambda self: mpl.rcParams['keymap.save'])
640644

641645

@@ -710,7 +714,7 @@ class ToolZoom(ZoomPanBase):
710714
"""A Tool for zooming using a rectangle selector."""
711715

712716
description = 'Zoom to rectangle'
713-
image = 'zoom_to_rect'
717+
image = 'mpl-data/images/zoom_to_rect'
714718
default_keymap = property(lambda self: mpl.rcParams['keymap.zoom'])
715719
cursor = cursors.SELECT_REGION
716720
radio_group = 'default'
@@ -832,7 +836,7 @@ class ToolPan(ZoomPanBase):
832836

833837
default_keymap = property(lambda self: mpl.rcParams['keymap.pan'])
834838
description = 'Pan axes with left mouse, zoom with right'
835-
image = 'move'
839+
image = 'mpl-data/images/move'
836840
cursor = cursors.MOVE
837841
radio_group = 'default'
838842

@@ -896,7 +900,7 @@ def _mouse_move(self, event):
896900
class ToolHelpBase(ToolBase):
897901
description = 'Print tool list, shortcuts and description'
898902
default_keymap = property(lambda self: mpl.rcParams['keymap.help'])
899-
image = 'help'
903+
image = 'mpl-data/images/help'
900904

901905
@staticmethod
902906
def format_shortcut(key_sequence):

0 commit comments

Comments
 (0)
0