diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 9c9249da8fb8..a884cc838860 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -11,6 +11,7 @@ `matplotlib.backend_managers.ToolManager` """ +import re import time import warnings from weakref import WeakKeyDictionary @@ -403,7 +404,7 @@ def trigger(self, sender, event, data=None): class ToolEnableAllNavigation(ToolBase): """Tool to enable all axes for toolmanager interaction""" - description = 'Enables all axes toolmanager' + description = 'Enable all axes toolmanager' default_keymap = rcParams['keymap.all_axes'] def trigger(self, sender, event, data=None): @@ -419,7 +420,7 @@ def trigger(self, sender, event, data=None): class ToolEnableNavigation(ToolBase): """Tool to enable a specific axes for toolmanager interaction""" - description = 'Enables one axes toolmanager' + description = 'Enable one axes toolmanager' default_keymap = (1, 2, 3, 4, 5, 6, 7, 8, 9) def trigger(self, sender, event, data=None): @@ -470,7 +471,7 @@ def _get_uniform_grid_state(ticks): class ToolGrid(_ToolGridBase): """Tool to toggle the major grids of the figure""" - description = 'Toogle major grids' + description = 'Toggle major grids' default_keymap = rcParams['keymap.grid'] def _get_next_grid_states(self, ax): @@ -491,7 +492,7 @@ def _get_next_grid_states(self, ax): class ToolMinorGrid(_ToolGridBase): """Tool to toggle the major and minor grids of the figure""" - description = 'Toogle major and minor grids' + description = 'Toggle major and minor grids' default_keymap = rcParams['keymap.grid_minor'] def _get_next_grid_states(self, ax): @@ -511,7 +512,7 @@ def _get_next_grid_states(self, ax): class ToolFullScreen(ToolToggleBase): """Tool to toggle full screen""" - description = 'Toogle Fullscreen mode' + description = 'Toggle fullscreen mode' default_keymap = rcParams['keymap.fullscreen'] def enable(self, event): @@ -541,7 +542,7 @@ def disable(self, event): class ToolYScale(AxisScaleBase): """Tool to toggle between linear and logarithmic scales on the Y axis""" - description = 'Toogle Scale Y axis' + description = 'Toggle scale Y axis' default_keymap = rcParams['keymap.yscale'] def set_scale(self, ax, scale): @@ -551,7 +552,7 @@ def set_scale(self, ax, scale): class ToolXScale(AxisScaleBase): """Tool to toggle between linear and logarithmic scales on the X axis""" - description = 'Toogle Scale X axis' + description = 'Toggle scale X axis' default_keymap = rcParams['keymap.xscale'] def set_scale(self, ax, scale): @@ -1020,6 +1021,48 @@ def _mouse_move(self, event): self.toolmanager.canvas.draw_idle() +class ToolHelpBase(ToolBase): + description = 'Print tool list, shortcuts and description' + default_keymap = rcParams['keymap.help'] + image = 'help.png' + + @staticmethod + def format_shortcut(key_sequence): + """ + Converts a shortcut string from the notation used in rc config to the + standard notation for displaying shortcuts, e.g. 'ctrl+a' -> 'Ctrl+A'. + """ + return (key_sequence if len(key_sequence) == 1 else + re.sub(r"\+[A-Z]", r"+Shift\g<0>", key_sequence).title()) + + def _format_tool_keymap(self, name): + keymaps = self.toolmanager.get_tool_keymap(name) + return ", ".join(self.format_shortcut(keymap) for keymap in keymaps) + + def _get_help_text(self): + entries = [] + for name, tool in sorted(self.toolmanager.tools.items()): + if not tool.description: + continue + entries.append( + "{}: {}\n\t{}".format( + name, self._format_tool_keymap(name), tool.description)) + return "\n".join(entries) + + def _get_help_html(self): + fmt = "{}{}{}" + rows = [fmt.format( + "Action", "Shortcuts", "Description")] + for name, tool in sorted(self.toolmanager.tools.items()): + if not tool.description: + continue + rows.append(fmt.format( + name, self._format_tool_keymap(name), tool.description)) + return ("" + "" + rows[0] + "" + "".join(rows[1:]) + "
") + + default_tools = {'home': ToolHome, 'back': ToolBack, 'forward': ToolForward, 'zoom': ToolZoom, 'pan': ToolPan, 'subplots': 'ToolConfigureSubplots', @@ -1037,12 +1080,13 @@ def _mouse_move(self, event): _views_positions: ToolViewsPositions, 'cursor': 'ToolSetCursor', 'rubberband': 'ToolRubberband', + 'help': 'ToolHelp', } """Default tools""" default_toolbar_tools = [['navigation', ['home', 'back', 'forward']], ['zoompan', ['pan', 'zoom', 'subplots']], - ['io', ['save']]] + ['io', ['save', 'help']]] """Default tools in the toolbar""" diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index fe0d393aa82b..164077cdc713 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -1,10 +1,11 @@ import six -from six.moves import tkinter as Tk import math import logging import os.path import sys +import tkinter as Tk +from tkinter.simpledialog import SimpleDialog import numpy as np @@ -963,10 +964,18 @@ def destroy(self, *args, **kwargs): self.window = None +class HelpTk(backend_tools.ToolHelpBase): + def trigger(self, *args): + dialog = SimpleDialog( + self.figure.canvas._tkcanvas, self._get_help_text(), ["OK"]) + dialog.done = lambda num: dialog.frame.master.withdraw() + + backend_tools.ToolSaveFigure = SaveFigureTk backend_tools.ToolConfigureSubplots = ConfigureSubplotsTk backend_tools.ToolSetCursor = SetCursorTk backend_tools.ToolRubberband = RubberbandTk +backend_tools.ToolHelp = HelpTk Toolbar = ToolbarTk diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a73cca9ff1b9..5ed0a8db2d3d 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -846,6 +846,16 @@ def trigger(self, sender, event, data=None): self.window.present() +class HelpGTK3(backend_tools.ToolHelpBase): + def trigger(self, *args): + dialog = Gtk.MessageDialog( + self._figure.canvas.get_toplevel(), + 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, self._get_help_text(), + title="Help") + dialog.run() + dialog.destroy() + + # Define the file to use as the GTk icon if sys.platform == 'win32': icon_filename = 'matplotlib.png' @@ -877,6 +887,7 @@ def error_msg_gtk(msg, parent=None): backend_tools.ToolConfigureSubplots = ConfigureSubplotsGTK3 backend_tools.ToolSetCursor = SetCursorGTK3 backend_tools.ToolRubberband = RubberbandGTK3 +backend_tools.ToolHelp = HelpGTK3 Toolbar = ToolbarGTK3 diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 0a0bf22e0291..5b94fd19d84b 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -1064,10 +1064,16 @@ def remove_rubberband(self): self.canvas.drawRectangle(None) +class HelpQt(backend_tools.ToolHelpBase): + def trigger(self, *args): + QtWidgets.QMessageBox.information(None, "Help", self._get_help_html()) + + backend_tools.ToolSaveFigure = SaveFigureQt backend_tools.ToolConfigureSubplots = ConfigureSubplotsQt backend_tools.ToolSetCursor = SetCursorQt backend_tools.ToolRubberband = RubberbandQt +backend_tools.ToolHelp = HelpQt def error_msg_qt(msg, parent=None): diff --git a/lib/matplotlib/mpl-data/images/help.pdf b/lib/matplotlib/mpl-data/images/help.pdf new file mode 100644 index 000000000000..38178d05b272 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help.pdf differ diff --git a/lib/matplotlib/mpl-data/images/help.png b/lib/matplotlib/mpl-data/images/help.png new file mode 100644 index 000000000000..a52fbbe819e2 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help.png differ diff --git a/lib/matplotlib/mpl-data/images/help.ppm b/lib/matplotlib/mpl-data/images/help.ppm new file mode 100644 index 000000000000..aed6f506df4d Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help.ppm differ diff --git a/lib/matplotlib/mpl-data/images/help.svg b/lib/matplotlib/mpl-data/images/help.svg new file mode 100644 index 000000000000..484bdbcbf659 --- /dev/null +++ b/lib/matplotlib/mpl-data/images/help.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/mpl-data/images/help_large.png b/lib/matplotlib/mpl-data/images/help_large.png new file mode 100644 index 000000000000..3f3d4dfed7e9 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help_large.png differ diff --git a/lib/matplotlib/mpl-data/images/help_large.ppm b/lib/matplotlib/mpl-data/images/help_large.ppm new file mode 100644 index 000000000000..4cf30807b0a1 Binary files /dev/null and b/lib/matplotlib/mpl-data/images/help_large.ppm differ diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 838af4d974ac..dcd2e6853b86 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1420,6 +1420,7 @@ def _validate_linestyle(ls): 'keymap.yscale': [['l'], validate_stringlist], 'keymap.xscale': [['k', 'L'], validate_stringlist], 'keymap.all_axes': [['a'], validate_stringlist], + 'keymap.help': [['f1'], validate_stringlist], # sample data 'examples.directory': ['', validate_string], diff --git a/lib/matplotlib/tests/test_backend_tools.py b/lib/matplotlib/tests/test_backend_tools.py new file mode 100644 index 000000000000..cc05a1a98f78 --- /dev/null +++ b/lib/matplotlib/tests/test_backend_tools.py @@ -0,0 +1,20 @@ +import pytest + +from matplotlib.backend_tools import ToolHelpBase + + +@pytest.mark.parametrize('rc_shortcut,expected', [ + ('home', 'Home'), + ('backspace', 'Backspace'), + ('f1', 'F1'), + ('ctrl+a', 'Ctrl+A'), + ('ctrl+A', 'Ctrl+Shift+A'), + ('a', 'a'), + ('A', 'A'), + ('ctrl+shift+f1', 'Ctrl+Shift+F1'), + ('1', '1'), + ('cmd+p', 'Cmd+P'), + ('cmd+1', 'Cmd+1'), +]) +def test_format_shortcut(rc_shortcut, expected): + assert ToolHelpBase.format_shortcut(rc_shortcut) == expected diff --git a/matplotlibrc.template b/matplotlibrc.template index cf4e0803106b..a3416144f0f0 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -583,6 +583,7 @@ backend : $TEMPLATE_BACKEND #keymap.pan : p ## pan mnemonic #keymap.zoom : o ## zoom mnemonic #keymap.save : s, ctrl+s ## saving current figure +#keymap.help : f1 ## display help about active tools #keymap.quit : ctrl+w, cmd+w, q ## close the current figure #keymap.quit_all : W, cmd+W, Q ## close all figures #keymap.grid : g ## switching on/off major grids in current axes diff --git a/tools/make_icons.py b/tools/make_icons.py index 3c9712fa4038..53bb1f023bb3 100755 --- a/tools/make_icons.py +++ b/tools/make_icons.py @@ -97,7 +97,8 @@ def make_matplotlib_icon(): ('move', 0xf047), ('filesave', 0xf0c7), ('subplots', 0xf1de), - ('qt4_editor_options', 0xf201)] + ('qt4_editor_options', 0xf201), + ('help', 0xf128)] def make_icons():