diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index ef591900ebad..a10dd19a1788 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -24,7 +24,6 @@ from matplotlib.widgets import SubplotTool from .qt_compat import QtCore, QtWidgets, _getSaveFileName, __version__ -from matplotlib.backends.qt_editor.formsubplottool import UiSubplotTool from .backend_qt5 import (backend_version, SPECIAL_KEYS, SUPER, ALT, CTRL, SHIFT, MODIFIER_KEYS, fn_name, cursord, diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 109f8233ff6a..d23bf13f7d76 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -21,7 +21,6 @@ from matplotlib._pylab_helpers import Gcf from matplotlib.figure import Figure -from matplotlib.widgets import SubplotTool import matplotlib.backends.qt_editor.figureoptions as figureoptions from .qt_compat import (QtCore, QtGui, QtWidgets, _getSaveFileName, @@ -778,101 +777,72 @@ def save_figure(self, *args): QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton) -class SubplotToolQt(SubplotTool, UiSubplotTool): +class SubplotToolQt(UiSubplotTool): def __init__(self, targetfig, parent): UiSubplotTool.__init__(self, None) - self.targetfig = targetfig - self.parent = parent - self.donebutton.clicked.connect(self.close) - self.resetbutton.clicked.connect(self.reset) - self.tightlayout.clicked.connect(self.functight) - - # constraints - self.sliderleft.valueChanged.connect(self.sliderright.setMinimum) - self.sliderright.valueChanged.connect(self.sliderleft.setMaximum) - self.sliderbottom.valueChanged.connect(self.slidertop.setMinimum) - self.slidertop.valueChanged.connect(self.sliderbottom.setMaximum) - - self.defaults = {} - for attr in ('left', 'bottom', 'right', 'top', 'wspace', 'hspace', ): - val = getattr(self.targetfig.subplotpars, attr) - self.defaults[attr] = val - slider = getattr(self, 'slider' + attr) - txt = getattr(self, attr + 'value') - slider.setMinimum(0) - slider.setMaximum(1000) - slider.setSingleStep(5) - # do this before hooking up the callbacks - slider.setSliderPosition(int(val * 1000)) - txt.setText("%.2f" % val) - slider.valueChanged.connect(getattr(self, 'func' + attr)) - self._setSliderPositions() - - def _setSliderPositions(self): - for attr in ('left', 'bottom', 'right', 'top', 'wspace', 'hspace', ): - slider = getattr(self, 'slider' + attr) - slider.setSliderPosition(int(self.defaults[attr] * 1000)) - - def funcleft(self, val): - if val == self.sliderright.value(): - val -= 1 - val /= 1000. - self.targetfig.subplots_adjust(left=val) - self.leftvalue.setText("%.2f" % val) - if self.drawon: - self.targetfig.canvas.draw_idle() - - def funcright(self, val): - if val == self.sliderleft.value(): - val += 1 - val /= 1000. - self.targetfig.subplots_adjust(right=val) - self.rightvalue.setText("%.2f" % val) - if self.drawon: - self.targetfig.canvas.draw_idle() - - def funcbottom(self, val): - if val == self.slidertop.value(): - val -= 1 - val /= 1000. - self.targetfig.subplots_adjust(bottom=val) - self.bottomvalue.setText("%.2f" % val) - if self.drawon: - self.targetfig.canvas.draw_idle() - - def functop(self, val): - if val == self.sliderbottom.value(): - val += 1 - val /= 1000. - self.targetfig.subplots_adjust(top=val) - self.topvalue.setText("%.2f" % val) - if self.drawon: - self.targetfig.canvas.draw_idle() - - def funcwspace(self, val): - val /= 1000. - self.targetfig.subplots_adjust(wspace=val) - self.wspacevalue.setText("%.2f" % val) - if self.drawon: - self.targetfig.canvas.draw_idle() - - def funchspace(self, val): - val /= 1000. - self.targetfig.subplots_adjust(hspace=val) - self.hspacevalue.setText("%.2f" % val) - if self.drawon: - self.targetfig.canvas.draw_idle() - - def functight(self): - self.targetfig.tight_layout() - self._setSliderPositions() - self.targetfig.canvas.draw_idle() - - def reset(self): - self.targetfig.subplots_adjust(**self.defaults) - self._setSliderPositions() - self.targetfig.canvas.draw_idle() + self._figure = targetfig + + for lower, higher in [("bottom", "top"), ("left", "right")]: + self._widgets[lower].valueChanged.connect( + lambda val: self._widgets[higher].setMinimum(val + .001)) + self._widgets[higher].valueChanged.connect( + lambda val: self._widgets[lower].setMaximum(val - .001)) + + self._attrs = ["top", "bottom", "left", "right", "hspace", "wspace"] + self._defaults = {attr: vars(self._figure.subplotpars)[attr] + for attr in self._attrs} + + # Set values after setting the range callbacks, but before setting up + # the redraw callbacks. + self._reset() + + for attr in self._attrs: + self._widgets[attr].valueChanged.connect(self._on_value_changed) + for action, method in [("Export values", self._export_values), + ("Tight layout", self._tight_layout), + ("Reset", self._reset), + ("Close", self.close)]: + self._widgets[action].clicked.connect(method) + + def _export_values(self): + # Explicitly round to 3 decimals (which is also the spinbox precision) + # to avoid numbers of the form 0.100...001. + dialog = QtWidgets.QDialog() + layout = QtWidgets.QVBoxLayout() + dialog.setLayout(layout) + text = QtWidgets.QPlainTextEdit() + text.setReadOnly(True) + layout.addWidget(text) + text.setPlainText( + ",\n".join("{}={:.3}".format(attr, self._widgets[attr].value()) + for attr in self._attrs)) + # Adjust the height of the text widget to fit the whole text, plus + # some padding. + size = text.maximumSize() + size.setHeight( + QtGui.QFontMetrics(text.document().defaultFont()) + .size(0, text.toPlainText()).height() + 20) + text.setMaximumSize(size) + dialog.exec_() + + def _on_value_changed(self): + self._figure.subplots_adjust(**{attr: self._widgets[attr].value() + for attr in self._attrs}) + self._figure.canvas.draw_idle() + + def _tight_layout(self): + self._figure.tight_layout() + for attr in self._attrs: + widget = self._widgets[attr] + widget.blockSignals(True) + widget.setValue(vars(self._figure.subplotpars)[attr]) + widget.blockSignals(False) + self._figure.canvas.draw_idle() + + def _reset(self): + for attr, value in self._defaults.items(): + self._widgets[attr].setValue(value) def error_msg_qt(msg, parent=None): diff --git a/lib/matplotlib/backends/qt_editor/formsubplottool.py b/lib/matplotlib/backends/qt_editor/formsubplottool.py index ee92cae41ec7..4bce2824707f 100644 --- a/lib/matplotlib/backends/qt_editor/formsubplottool.py +++ b/lib/matplotlib/backends/qt_editor/formsubplottool.py @@ -1,230 +1,56 @@ -# -*- coding: utf-8 -*- -""" -formsubplottool.py - -backend.qt4 (PyQt4|PySide) independent form of the subplot tool. - -""" from matplotlib.backends.qt_compat import QtCore, QtGui, QtWidgets -__author__ = 'rudolf.hoefler@gmail.com' - class UiSubplotTool(QtWidgets.QDialog): def __init__(self, *args, **kwargs): super(UiSubplotTool, self).__init__(*args, **kwargs) - self.setObjectName('SubplotTool') - self.resize(450, 265) - - gbox = QtWidgets.QGridLayout(self) - self.setLayout(gbox) - - # groupbox borders - groupbox = QtWidgets.QGroupBox('Borders', self) - gbox.addWidget(groupbox, 6, 0, 1, 1) - self.verticalLayout = QtWidgets.QVBoxLayout(groupbox) - self.verticalLayout.setSpacing(0) - - # slider top - self.hboxtop = QtWidgets.QHBoxLayout() - self.labeltop = QtWidgets.QLabel('top', self) - self.labeltop.setMinimumSize(QtCore.QSize(50, 0)) - self.labeltop.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.slidertop = QtWidgets.QSlider(self) - self.slidertop.setMouseTracking(False) - self.slidertop.setProperty("value", 0) - self.slidertop.setOrientation(QtCore.Qt.Horizontal) - self.slidertop.setInvertedAppearance(False) - self.slidertop.setInvertedControls(False) - self.slidertop.setTickPosition(QtWidgets.QSlider.TicksAbove) - self.slidertop.setTickInterval(100) - - self.topvalue = QtWidgets.QLabel('0', self) - self.topvalue.setMinimumSize(QtCore.QSize(30, 0)) - self.topvalue.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.verticalLayout.addLayout(self.hboxtop) - self.hboxtop.addWidget(self.labeltop) - self.hboxtop.addWidget(self.slidertop) - self.hboxtop.addWidget(self.topvalue) - - # slider bottom - hboxbottom = QtWidgets.QHBoxLayout() - labelbottom = QtWidgets.QLabel('bottom', self) - labelbottom.setMinimumSize(QtCore.QSize(50, 0)) - labelbottom.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.sliderbottom = QtWidgets.QSlider(self) - self.sliderbottom.setMouseTracking(False) - self.sliderbottom.setProperty("value", 0) - self.sliderbottom.setOrientation(QtCore.Qt.Horizontal) - self.sliderbottom.setInvertedAppearance(False) - self.sliderbottom.setInvertedControls(False) - self.sliderbottom.setTickPosition(QtWidgets.QSlider.TicksAbove) - self.sliderbottom.setTickInterval(100) - - self.bottomvalue = QtWidgets.QLabel('0', self) - self.bottomvalue.setMinimumSize(QtCore.QSize(30, 0)) - self.bottomvalue.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.verticalLayout.addLayout(hboxbottom) - hboxbottom.addWidget(labelbottom) - hboxbottom.addWidget(self.sliderbottom) - hboxbottom.addWidget(self.bottomvalue) - - # slider left - hboxleft = QtWidgets.QHBoxLayout() - labelleft = QtWidgets.QLabel('left', self) - labelleft.setMinimumSize(QtCore.QSize(50, 0)) - labelleft.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.sliderleft = QtWidgets.QSlider(self) - self.sliderleft.setMouseTracking(False) - self.sliderleft.setProperty("value", 0) - self.sliderleft.setOrientation(QtCore.Qt.Horizontal) - self.sliderleft.setInvertedAppearance(False) - self.sliderleft.setInvertedControls(False) - self.sliderleft.setTickPosition(QtWidgets.QSlider.TicksAbove) - self.sliderleft.setTickInterval(100) - - self.leftvalue = QtWidgets.QLabel('0', self) - self.leftvalue.setMinimumSize(QtCore.QSize(30, 0)) - self.leftvalue.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.verticalLayout.addLayout(hboxleft) - hboxleft.addWidget(labelleft) - hboxleft.addWidget(self.sliderleft) - hboxleft.addWidget(self.leftvalue) - - # slider right - hboxright = QtWidgets.QHBoxLayout() - self.labelright = QtWidgets.QLabel('right', self) - self.labelright.setMinimumSize(QtCore.QSize(50, 0)) - self.labelright.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.sliderright = QtWidgets.QSlider(self) - self.sliderright.setMouseTracking(False) - self.sliderright.setProperty("value", 0) - self.sliderright.setOrientation(QtCore.Qt.Horizontal) - self.sliderright.setInvertedAppearance(False) - self.sliderright.setInvertedControls(False) - self.sliderright.setTickPosition(QtWidgets.QSlider.TicksAbove) - self.sliderright.setTickInterval(100) - - self.rightvalue = QtWidgets.QLabel('0', self) - self.rightvalue.setMinimumSize(QtCore.QSize(30, 0)) - self.rightvalue.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.verticalLayout.addLayout(hboxright) - hboxright.addWidget(self.labelright) - hboxright.addWidget(self.sliderright) - hboxright.addWidget(self.rightvalue) - - # groupbox spacings - groupbox = QtWidgets.QGroupBox('Spacings', self) - gbox.addWidget(groupbox, 7, 0, 1, 1) - self.verticalLayout = QtWidgets.QVBoxLayout(groupbox) - self.verticalLayout.setSpacing(0) - - # slider hspace - hboxhspace = QtWidgets.QHBoxLayout() - self.labelhspace = QtWidgets.QLabel('hspace', self) - self.labelhspace.setMinimumSize(QtCore.QSize(50, 0)) - self.labelhspace.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.sliderhspace = QtWidgets.QSlider(self) - self.sliderhspace.setMouseTracking(False) - self.sliderhspace.setProperty("value", 0) - self.sliderhspace.setOrientation(QtCore.Qt.Horizontal) - self.sliderhspace.setInvertedAppearance(False) - self.sliderhspace.setInvertedControls(False) - self.sliderhspace.setTickPosition(QtWidgets.QSlider.TicksAbove) - self.sliderhspace.setTickInterval(100) - - self.hspacevalue = QtWidgets.QLabel('0', self) - self.hspacevalue.setMinimumSize(QtCore.QSize(30, 0)) - self.hspacevalue.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.verticalLayout.addLayout(hboxhspace) - hboxhspace.addWidget(self.labelhspace) - hboxhspace.addWidget(self.sliderhspace) - hboxhspace.addWidget(self.hspacevalue) # slider hspace - - # slider wspace - hboxwspace = QtWidgets.QHBoxLayout() - self.labelwspace = QtWidgets.QLabel('wspace', self) - self.labelwspace.setMinimumSize(QtCore.QSize(50, 0)) - self.labelwspace.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.sliderwspace = QtWidgets.QSlider(self) - self.sliderwspace.setMouseTracking(False) - self.sliderwspace.setProperty("value", 0) - self.sliderwspace.setOrientation(QtCore.Qt.Horizontal) - self.sliderwspace.setInvertedAppearance(False) - self.sliderwspace.setInvertedControls(False) - self.sliderwspace.setTickPosition(QtWidgets.QSlider.TicksAbove) - self.sliderwspace.setTickInterval(100) - - self.wspacevalue = QtWidgets.QLabel('0', self) - self.wspacevalue.setMinimumSize(QtCore.QSize(30, 0)) - self.wspacevalue.setAlignment( - QtCore.Qt.AlignRight | - QtCore.Qt.AlignTrailing | - QtCore.Qt.AlignVCenter) - - self.verticalLayout.addLayout(hboxwspace) - hboxwspace.addWidget(self.labelwspace) - hboxwspace.addWidget(self.sliderwspace) - hboxwspace.addWidget(self.wspacevalue) - - # button bar - hbox2 = QtWidgets.QHBoxLayout() - gbox.addLayout(hbox2, 8, 0, 1, 1) - self.tightlayout = QtWidgets.QPushButton('Tight Layout', self) - spacer = QtWidgets.QSpacerItem( - 5, 20, QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Minimum) - self.resetbutton = QtWidgets.QPushButton('Reset', self) - self.donebutton = QtWidgets.QPushButton('Close', self) - self.donebutton.setFocus() - hbox2.addWidget(self.tightlayout) - hbox2.addItem(spacer) - hbox2.addWidget(self.resetbutton) - hbox2.addWidget(self.donebutton) - - self.donebutton.clicked.connect(self.accept) + self.setObjectName("SubplotTool") + self._widgets = {} + + layout = QtWidgets.QHBoxLayout() + self.setLayout(layout) + + left = QtWidgets.QVBoxLayout() + layout.addLayout(left) + right = QtWidgets.QVBoxLayout() + layout.addLayout(right) + + box = QtWidgets.QGroupBox("Borders") + left.addWidget(box) + inner = QtWidgets.QFormLayout(box) + for side in ["top", "bottom", "left", "right"]: + self._widgets[side] = widget = QtWidgets.QDoubleSpinBox() + widget.setMinimum(0) + widget.setMaximum(1) + widget.setDecimals(3) + widget.setSingleStep(.005) + widget.setKeyboardTracking(False) + inner.addRow(side, widget) + left.addStretch(1) + + box = QtWidgets.QGroupBox("Spacings") + right.addWidget(box) + inner = QtWidgets.QFormLayout(box) + for side in ["hspace", "wspace"]: + self._widgets[side] = widget = QtWidgets.QDoubleSpinBox() + widget.setMinimum(0) + widget.setMaximum(1) + widget.setDecimals(3) + widget.setSingleStep(.005) + widget.setKeyboardTracking(False) + inner.addRow(side, widget) + right.addStretch(1) + + widget = QtWidgets.QPushButton("Export values") + self._widgets["Export values"] = widget + # Don't trigger on , which is used to input values. + widget.setAutoDefault(False) + left.addWidget(widget) + + for action in ["Tight layout", "Reset", "Close"]: + self._widgets[action] = widget = QtWidgets.QPushButton(action) + widget.setAutoDefault(False) + right.addWidget(widget) + + self._widgets["Close"].setFocus()