From 7d72aa6c86a98c9d30eea6bca88044990d69c390 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 29 Dec 2016 01:00:42 +0100 Subject: [PATCH 1/2] Interactive setting of savefig rcParams (Qt only). An additional window pops after selecting the filename to allow setting of savefig-related rcParams. For simplicity and consistency, the "ps.usedistiller" rcParam is now normalized to `None` when not set. --- lib/matplotlib/backends/backend_qt5.py | 118 ++++++++++++++++++++++--- lib/matplotlib/rcsetup.py | 6 +- matplotlibrc.template | 12 +-- 3 files changed, 112 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 18fa2ff04ca9..982ae7d65273 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -12,18 +12,17 @@ import matplotlib +from matplotlib import backend_tools from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, - TimerBase, cursors, ToolContainerBase, StatusbarBase) -import matplotlib.backends.qt_editor.figureoptions as figureoptions -from matplotlib.backends.qt_editor.formsubplottool import UiSubplotTool -from matplotlib.figure import Figure + StatusbarBase, TimerBase, ToolContainerBase, cursors) from matplotlib.backend_managers import ToolManager -from matplotlib import backend_tools from .qt_compat import ( QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API) +from .qt_editor import figureoptions, formlayout +from .qt_editor.formsubplottool import UiSubplotTool backend_version = __version__ @@ -838,24 +837,42 @@ def save_figure(self, *args): startpath = os.path.expanduser( matplotlib.rcParams['savefig.directory']) start = os.path.join(startpath, self.canvas.get_default_filename()) + filters = [] - selectedFilter = None + selected_filter = None for name, exts in sorted_filetypes: - exts_list = " ".join(['*.%s' % ext for ext in exts]) - filter = '%s (%s)' % (name, exts_list) + filters.append( + '{} ({})'.format(name, ' '.join(map('*.{}'.format, exts)))) if default_filetype in exts: - selectedFilter = filter - filters.append(filter) + selected_filter = filters[-1] filters = ';;'.join(filters) - fname, filter = _getSaveFileName(self.parent, - "Choose a filename to save to", - start, filters, selectedFilter) + fname, selected_filter = _getSaveFileName( + self.parent, "Choose a filename to save to", + start, filters, selected_filter) if fname: - # Save dir for next time, unless empty str (i.e., use cwd). if startpath != "": matplotlib.rcParams['savefig.directory'] = ( os.path.dirname(six.text_type(fname))) + options = _default_savefig_options + try: + options = (options + + [None] + + _extra_savefig_options[ + os.path.splitext(fname)[1].lower()]) + except KeyError: + pass + fedit_arg = [] + for option in options: + if option is None: + fedit_arg.append((None, None)) + else: + fedit_arg.append(option.make_fedit_entry()) + fedit_res = formlayout.fedit(fedit_arg, "Options") + if not fedit_res: + return + for option, res in zip(filter(None, options), fedit_res): + option.setter(res) try: self.canvas.figure.savefig(six.text_type(fname)) except Exception as e: @@ -864,6 +881,79 @@ def save_figure(self, *args): QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton) +class _Option(object): + + def __init__(self, name, rc_key=None, getter=None, setter=None): + if rc_key and not (getter or setter): + def make_fedit_entry(): + return (name, matplotlib.rcParams[rc_key]) + + def setter(val): + matplotlib.rcParams[rc_key] = val + + elif getter and setter and not rc_key: + def make_fedit_entry(): + return (name, getter()) + + else: + raise ValueError("Invalid entry") + + self.name = name + self.make_fedit_entry = make_fedit_entry + self.setter = setter + + +_default_savefig_options = [ + _Option("DPI", "savefig.dpi"), + _Option("Face color", "savefig.facecolor"), + _Option("Edge color", "savefig.edgecolor"), + _Option("Tight bounding box", + getter=lambda: matplotlib.rcParams["savefig.bbox"] == "tight", + setter=lambda val: matplotlib.rcParams.__setitem__( + "savefig.bbox", "tight" if val else "standard")), + _Option("Tight bounding box padding", "savefig.pad_inches"), + _Option("Transparent background", "savefig.transparent")] + + +_extra_savefig_options = { + ".jpg": [ + _Option("JPEG quality", "savefig.jpeg_quality")], + ".jpeg": [ + _Option("JPEG quality", "savefig.jpeg_quality")], + ".pdf": [ + _Option("PDF compression", "pdf.compression"), + _Option("PDF font type", + getter=lambda: + [str(matplotlib.rcParams["pdf.fonttype"]), + "3", "42"], + setter=lambda val: + matplotlib.rcParams.__setitem__("pdf.fonttype", val))], + ".ps": [ + _Option("PS paper size", + getter=lambda: + [matplotlib.rcParams["ps.papersize"]] + + ["auto", "letter", "legal", "ledger"] + + ["A{}".format(i) for i in range(11)] + + ["B{}".format(i) for i in range(11)], + setter=lambda val: + matplotlib.rcParams.__setitem__("ps.papersize", val)), + _Option("PS use AFM font", "ps.useafm"), + _Option("PS distiller", + getter=lambda: + [str(matplotlib.rcParams["ps.usedistiller"])] + + ["None", "ghostscript", "xpdf"], + setter=lambda val: + matplotlib.rcParams.__setitem__("ps.usedistiller", val)), + _Option("PS distiller resolution", "ps.distiller.res"), + _Option("PS font type", + getter=lambda: + [str(matplotlib.rcParams["ps.fonttype"]), + "3", "42"], + setter=lambda val: + matplotlib.rcParams.__setitem__("ps.fonttype", val))] +} + + class SubplotToolQt(UiSubplotTool): def __init__(self, targetfig, parent): UiSubplotTool.__init__(self, None) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index a4434f1ba5d4..2c85ce4f83ad 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -509,10 +509,8 @@ def update_savefig_format(value): def validate_ps_distiller(s): if isinstance(s, six.string_types): s = s.lower() - if s in ('none', None): + if s in ('none', None, 'false', False): return None - elif s in ('false', False): - return False elif s in ('ghostscript', 'xpdf'): return s else: @@ -1337,7 +1335,7 @@ def _validate_linestyle(ls): 'ps.papersize': ['letter', validate_ps_papersize], 'ps.useafm': [False, validate_bool], # Set PYTHONINSPECT # use ghostscript or xpdf to distill ps output - 'ps.usedistiller': [False, validate_ps_distiller], + 'ps.usedistiller': [None, validate_ps_distiller], 'ps.distiller.res': [6000, validate_int], # dpi 'ps.fonttype': [3, validate_fonttype], # 3 (Type3) or 42 (Truetype) # compression level from 0 to 9; 0 to disable diff --git a/matplotlibrc.template b/matplotlibrc.template index aec7366fdd82..1ba67d214888 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -531,12 +531,12 @@ backend : $TEMPLATE_BACKEND # ps backend params #ps.papersize : letter # auto, letter, legal, ledger, A0-A10, B0-B10 #ps.useafm : False # use of afm fonts, results in small files -#ps.usedistiller : False # can be: None, ghostscript or xpdf - # Experimental: may produce smaller files. - # xpdf intended for production of publication quality files, - # but requires ghostscript, xpdf and ps2eps -#ps.distiller.res : 6000 # dpi -#ps.fonttype : 3 # Output Type 3 (Type3) or Type 42 (TrueType) +#ps.usedistiller : None # can be: None, ghostscript or xpdf + # Experimental: may produce smaller files. + # xpdf intended for production of publication quality files, + # but requires ghostscript, xpdf and ps2eps +#ps.distiller.res : 6000 # dpi +#ps.fonttype : 3 # Output Type 3 (Type3) or Type 42 (TrueType) ## pdf backend params #pdf.compression : 6 # integer from 0 to 9 From 2129eb05e54cfb4826a6f3feaea06a24f38f6744 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 4 Jun 2017 14:40:25 -0700 Subject: [PATCH 2/2] Alternative UI, using a toolbar menu. --- lib/matplotlib/backends/backend_qt5.py | 98 +++++++++++++------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 982ae7d65273..166f88b12cff 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -724,6 +724,13 @@ def _init_toolbar(self): self._actions[callback] = a if callback in ['zoom', 'pan']: a.setCheckable(True) + if callback == 'save_figure': + btn = self.widgetForAction(a) + btn.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) + menu = a.__menu = QtWidgets.QMenu() + a.setMenu(menu) + menu.addAction( + 'Options for saving', self._ui_get_save_options) if tooltip_text is not None: a.setToolTip(tooltip_text) if text == 'Subplots': @@ -854,25 +861,6 @@ def save_figure(self, *args): if startpath != "": matplotlib.rcParams['savefig.directory'] = ( os.path.dirname(six.text_type(fname))) - options = _default_savefig_options - try: - options = (options - + [None] - + _extra_savefig_options[ - os.path.splitext(fname)[1].lower()]) - except KeyError: - pass - fedit_arg = [] - for option in options: - if option is None: - fedit_arg.append((None, None)) - else: - fedit_arg.append(option.make_fedit_entry()) - fedit_res = formlayout.fedit(fedit_arg, "Options") - if not fedit_res: - return - for option, res in zip(filter(None, options), fedit_res): - option.setter(res) try: self.canvas.figure.savefig(six.text_type(fname)) except Exception as e: @@ -880,6 +868,25 @@ def save_figure(self, *args): self, "Error saving file", six.text_type(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton) + def _ui_get_save_options(self): + fedit_arg = [] + for tab_name, options in _savefig_options: + fedit_arg.append(([], tab_name, "")) + for option in options: + if option is None: + fedit_arg[-1][0].append((None, None)) + else: + fedit_arg[-1][0].append(option.make_fedit_entry()) + fedit_res = formlayout.fedit(fedit_arg, "Options") + if not fedit_res: + return + for option, res in zip( + [option + for tab_name, options in _savefig_options + for option in options], + [res for tab in fedit_res for res in tab]): + option.setter(res) + class _Option(object): @@ -903,33 +910,29 @@ def make_fedit_entry(): self.setter = setter -_default_savefig_options = [ - _Option("DPI", "savefig.dpi"), - _Option("Face color", "savefig.facecolor"), - _Option("Edge color", "savefig.edgecolor"), - _Option("Tight bounding box", - getter=lambda: matplotlib.rcParams["savefig.bbox"] == "tight", - setter=lambda val: matplotlib.rcParams.__setitem__( - "savefig.bbox", "tight" if val else "standard")), - _Option("Tight bounding box padding", "savefig.pad_inches"), - _Option("Transparent background", "savefig.transparent")] - - -_extra_savefig_options = { - ".jpg": [ - _Option("JPEG quality", "savefig.jpeg_quality")], - ".jpeg": [ - _Option("JPEG quality", "savefig.jpeg_quality")], - ".pdf": [ - _Option("PDF compression", "pdf.compression"), - _Option("PDF font type", +_savefig_options = [ + ("Common", [ + _Option("DPI", "savefig.dpi"), + _Option("Face color", "savefig.facecolor"), + _Option("Edge color", "savefig.edgecolor"), + _Option("Tight bounding box", + getter=lambda: matplotlib.rcParams["savefig.bbox"] == "tight", + setter=lambda val: matplotlib.rcParams.__setitem__( + "savefig.bbox", "tight" if val else "standard")), + _Option("Tight bounding box padding", "savefig.pad_inches"), + _Option("Transparent background", "savefig.transparent")]), + ("JPEG", [ + _Option("Quality", "savefig.jpeg_quality")]), + ("PDF", [ + _Option("Compression", "pdf.compression"), + _Option("Font type", getter=lambda: [str(matplotlib.rcParams["pdf.fonttype"]), "3", "42"], setter=lambda val: - matplotlib.rcParams.__setitem__("pdf.fonttype", val))], - ".ps": [ - _Option("PS paper size", + matplotlib.rcParams.__setitem__("pdf.fonttype", val))]), + ("PS", [ + _Option("Paper size", getter=lambda: [matplotlib.rcParams["ps.papersize"]] + ["auto", "letter", "legal", "ledger"] @@ -937,21 +940,20 @@ def make_fedit_entry(): + ["B{}".format(i) for i in range(11)], setter=lambda val: matplotlib.rcParams.__setitem__("ps.papersize", val)), - _Option("PS use AFM font", "ps.useafm"), - _Option("PS distiller", + _Option("Use AFM font?", "ps.useafm"), + _Option("Distiller", getter=lambda: [str(matplotlib.rcParams["ps.usedistiller"])] + ["None", "ghostscript", "xpdf"], setter=lambda val: matplotlib.rcParams.__setitem__("ps.usedistiller", val)), - _Option("PS distiller resolution", "ps.distiller.res"), - _Option("PS font type", + _Option("Distiller resolution", "ps.distiller.res"), + _Option("Font type", getter=lambda: [str(matplotlib.rcParams["ps.fonttype"]), "3", "42"], setter=lambda val: - matplotlib.rcParams.__setitem__("ps.fonttype", val))] -} + matplotlib.rcParams.__setitem__("ps.fonttype", val))])] class SubplotToolQt(UiSubplotTool):