From 37d29df493354585404755eea85f42c266c0d8f1 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 13 Apr 2018 02:21:11 -0700 Subject: [PATCH 1/3] Help tool. --- lib/matplotlib/backend_tools.py | 54 +++++++++++++++--- lib/matplotlib/backends/_backend_tk.py | 11 +++- lib/matplotlib/backends/backend_gtk3.py | 11 ++++ lib/matplotlib/backends/backend_qt5.py | 6 ++ lib/matplotlib/mpl-data/images/help.pdf | Bin 0 -> 1813 bytes lib/matplotlib/mpl-data/images/help.png | Bin 0 -> 472 bytes lib/matplotlib/mpl-data/images/help.ppm | Bin 0 -> 1741 bytes lib/matplotlib/mpl-data/images/help.svg | 52 +++++++++++++++++ lib/matplotlib/mpl-data/images/help_large.png | Bin 0 -> 747 bytes lib/matplotlib/mpl-data/images/help_large.ppm | Bin 0 -> 6925 bytes lib/matplotlib/rcsetup.py | 1 + matplotlibrc.template | 1 + tools/make_icons.py | 3 +- 13 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 lib/matplotlib/mpl-data/images/help.pdf create mode 100644 lib/matplotlib/mpl-data/images/help.png create mode 100644 lib/matplotlib/mpl-data/images/help.ppm create mode 100644 lib/matplotlib/mpl-data/images/help.svg create mode 100644 lib/matplotlib/mpl-data/images/help_large.png create mode 100644 lib/matplotlib/mpl-data/images/help_large.ppm diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 9c9249da8fb8..0dfdfac8e261 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,42 @@ 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' + + def _format_tool_keymap(self, name): + keymaps = self.toolmanager.get_tool_keymap(name) + # Capitalize "ctrl+a" -> "Ctrl+A" but leave "a" as is. + return ", ".join(re.sub(r"\w{2,}|(?<=\+)\w", + lambda m: m.group(0).capitalize(), + 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 +1074,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 0000000000000000000000000000000000000000..38178d05b2725addadec9f4c005a1a764f1096d1 GIT binary patch literal 1813 zcmah~3rtg27|x;&*NlgQIp+fpOyr?(AARsrCbX0x7+6799kAWo9%@~CZ+Ckk1!Wly zK^)V_f=*u9WH=GnA_7s7IY!X94HAtEG0rGNCr)t!2`YP?(^5e&*-ew%^W}e>^PT_u zu3xfB9ZZGDBz_IoK>cY61xUcN+azn&03w+&Kr5gTq5(u|js*fj$%u@JH-M-p3FP!R zFl<^vtWAtd5g8EznIlQfb0UyB{wFh{2n7zv@lrx03%o7`ir`(;N2Nyfp-lutyczY3 znS3iJ-|-+F5Gh+3y^%8jg(F>?#BPH+5ro={Pi==H5#T&PG{^Cx6}3S5O0*Top;fq} zK%Bs5SpYt!G6fJSm~GTSO&n^CR;dERSuKnJIbDwP0p}M$PD2;!p zLo!YfoqTDamCq7%h(OHHQvxxk)7ZnCaqd=aG$Dcjp*A4~RZz$45#foDGl*LOh3`d! z6Rn~E8MDN8)%CgxUO>DhN;u1|M$FuSoZ|JW~?D{OgttH!Qp6kHDK z?F);FYRzdo@{RkM_V4+ewt)%5L{nbZWAotsob6A>Mkap0b!&9={QZ$?`q6in=bi6% z-@$$S;a_hJxe^z4483}>@9)}@gFk43*0X69+_iv%hua4_Jmpqx!Il?GKa0G%JT9^Q zrHW;DO1;LnMgG?22kD`6x$D5M-W}s!{>{y4m!g-rwoGh~{8`s|D4hVarb;0=V#`T@$sb{M{UDz_`tE-9S z_vh9o_wG+mJEEx6?+iaTP-;>E?Ezai968gqtStLMRbl74m7CXkWcLf&hyq#Pk*bra zNx9ZzwYDv>r{u#sTNdBW@AuVJ&c7~=NvqmFy0FsdD@06Epbs6pYH4M)&}&EMq<+{`Gq@`H66X4b?Tqyk*@ut!TXkWtQp=I-UQt? zyNtS3Y^ZZ@e!K3tTTPGe#k}emms^KkDK$SH{L$ihx4m^!&xr@ajW<5=(O&td-(>-t z8BP?Cg{i(LTNbVG9?$nIVN1IAvdb6$F_dvndHmA77jjPpd6)PsZg1FiQkqfoX5MCx z1Gg>pkLDM>qj{LXE`O5d(NXf}h}WENm!)6+Vv#xHVdqCw@2TQ{`@`Ss8e3a?_95$1 zk2N|uFOzDu&k-Itxh&kNV64x{8JyA)7O-Dq4Zg&9<7Nx_s98NZMI_jBZBq-6y zb1H0H0jda^q(aFsil$^TQYH^3W&R}TA1EP|IF1)UfSD03CSEidSwM%7A#{j^w~8_a zEe+AbY;u)B$4VK-NslLz*zM)s@Wc|0L@ dD^4-yLPQuQ(JC^6Xr~Yz34I0qVw2R8{{W}EW2OKA literal 0 HcmV?d00001 diff --git a/lib/matplotlib/mpl-data/images/help.png b/lib/matplotlib/mpl-data/images/help.png new file mode 100644 index 0000000000000000000000000000000000000000..a52fbbe819e2fcbb1c1d5f8f143fc8d069688b3a GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEX7WqAsj$Z!;#Vf4nJ zFmC{1M)f27Yk`76C9V-A!TD(=<%vb947rIV1v&X8IhjccWvNBQnfZANMtTN%MtTML z#U&B&jPiK&SODM>mC86_nJR{Ht~oqG92>H6!I&RfjDz$oMC;uzx5 z`F6@iFJ?y(*Ymt}PEDaloIF?8a{Q9ydQ{(WW636gEnW(~S@jMA{|{;XY-~sp7Sx?) z(_PD*cq7Fqn~5u~VnhD4>hlKqn7%&t$}w@9Jpe7mLmAJ!PXuc{1r zO&`P;h<;ugBl>m0`&l*Y?r2IG3O2hD$$M@uho zwR_=Yy?RF7@zS@alxkN7wZ*W{x+i5|wE6s2##OJR_vU^TTHB|58yE`=p00i_>zopr E02Lj!oB#j- literal 0 HcmV?d00001 diff --git a/lib/matplotlib/mpl-data/images/help.ppm b/lib/matplotlib/mpl-data/images/help.ppm new file mode 100644 index 0000000000000000000000000000000000000000..aed6f506df4d1a3394aafff9b7b25899d49f500c GIT binary patch literal 1741 zcmWGA<1#W)Ff!pXGBxG;KMICR2rys+BzX>-0VrZbnuekW8;2Nku<64hM$jlsJwPp3 z1qm93tP!pl%_z7y@eDK*AY9_LLsSkPLxAqVbPTE9!RjgU48e5Is5xjMfEHh9vcz+# JWe)K+0sz+CNh|;W literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3f3d4dfed7e99421bd024c14046bf4616b4ca2ff GIT binary patch literal 747 zcmVqZlEAow3ozPGS*Kh@=uh zt29YU!QR5c(o#^cu@D3i!6G7pMU;S|f`x@3Xc1C~VANnE8bvE}zs2r?guR>H+qXM8 z*dHDo`)=m{|CxC+vulzVWDLa?P!+9#*k}#JMr$B8S_84s8i^wG?Y0}H@YNgIJCvw{4NvH}_j zl94@6cqh8lSPhgAKTEP3g~V?H2e*v@l5O#R0pVHPDa}!%`28eBT~z!tV02p;0Coef zN{GKl)0eq`feQtNuLEqY{x&dHmCyZ^5;y`J2>j=Xq_ZAmb-dJ%1@eD@ zb6#Y182BB?zmc@&MdrSQB)v1UtH7b0bO*TZ$!BTd4J_*TF0_kelqYi1*?s?(8=v&002ovPDHLkV1l@aH3$Fz literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4cf30807b0a14c3aabf7267488cc16e61c4d357e GIT binary patch literal 6925 zcmeHFTN1)R2=jYS;TfFP=`pvCDP6_sBpOxQ3{kz?Z}qycY>AQ;;9Q*SSLX^8c;}(E~Qn{w55mc zAeBbYU6&G~u7%KZU(k|9+*j9PLtR9yTX!6#5pfr75Td$78izq6sg8A!XoD6#RHShg zI;sX#5i0sC Date: Wed, 18 Apr 2018 16:00:54 +0200 Subject: [PATCH 2/3] Fix short formatting for display --- lib/matplotlib/backend_tools.py | 24 ++++++++++++++++------ lib/matplotlib/tests/test_backend_tools.py | 20 ++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 lib/matplotlib/tests/test_backend_tools.py diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 0dfdfac8e261..7e33af8ca5e6 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -1026,13 +1026,24 @@ class ToolHelpBase(ToolBase): default_keymap = rcParams['keymap.help'] image = 'help.png' + + @staticmethod + def format_shortcut(keysequence): + """ + Converts a shortcut string from the notation used in rc config to the + standard notation for displaying shortcuts, e.g. 'ctrl+a' -> 'Ctrl+A'. + """ + def repl(match): + s = match.group(0) + return 'Shift+' + s if len( + s) == 1 and s.isupper() else s.capitalize() + if len(keysequence) == 1: + return keysequence # do not modify single characters + return re.sub(r"\w{2,}|(?<=\+)\w", repl, keysequence) + def _format_tool_keymap(self, name): keymaps = self.toolmanager.get_tool_keymap(name) - # Capitalize "ctrl+a" -> "Ctrl+A" but leave "a" as is. - return ", ".join(re.sub(r"\w{2,}|(?<=\+)\w", - lambda m: m.group(0).capitalize(), - keymap) - for keymap in keymaps) + return ", ".join(self.format_shortcut(keymap) for keymap in keymaps) def _get_help_text(self): entries = [] @@ -1053,7 +1064,8 @@ def _get_help_html(self): continue rows.append(fmt.format( name, self._format_tool_keymap(name), tool.description)) - return ("" + rows[0] + "" + return ("" + "
" + rows[0] + "" "".join(rows[1:]) + "
") diff --git a/lib/matplotlib/tests/test_backend_tools.py b/lib/matplotlib/tests/test_backend_tools.py new file mode 100644 index 000000000000..5c61ca004798 --- /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 \ No newline at end of file From 59cb786bc30a73fbe3c114ea4b8cdff338e9bb5c Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 18 Apr 2018 09:37:45 -0700 Subject: [PATCH 3/3] Simpler format_shortcut. --- lib/matplotlib/backend_tools.py | 12 +++--------- lib/matplotlib/tests/test_backend_tools.py | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 7e33af8ca5e6..a884cc838860 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -1026,20 +1026,14 @@ class ToolHelpBase(ToolBase): default_keymap = rcParams['keymap.help'] image = 'help.png' - @staticmethod - def format_shortcut(keysequence): + 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'. """ - def repl(match): - s = match.group(0) - return 'Shift+' + s if len( - s) == 1 and s.isupper() else s.capitalize() - if len(keysequence) == 1: - return keysequence # do not modify single characters - return re.sub(r"\w{2,}|(?<=\+)\w", repl, keysequence) + 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) diff --git a/lib/matplotlib/tests/test_backend_tools.py b/lib/matplotlib/tests/test_backend_tools.py index 5c61ca004798..cc05a1a98f78 100644 --- a/lib/matplotlib/tests/test_backend_tools.py +++ b/lib/matplotlib/tests/test_backend_tools.py @@ -17,4 +17,4 @@ ('cmd+1', 'Cmd+1'), ]) def test_format_shortcut(rc_shortcut, expected): - assert ToolHelpBase.format_shortcut(rc_shortcut) == expected \ No newline at end of file + assert ToolHelpBase.format_shortcut(rc_shortcut) == expected