class ColorButton(QtWidgets.QPushButton): """ Color choosing push button """ colorChanged = QtCore.Signal(QtGui.QColor) def __init__(self, parent=None): QtWidgets.QPushButton.__init__(self, parent) self.setFixedSize(20, 20) self.setIconSize(QtCore.QSize(12, 12)) self.clicked.connect(self.choose_color) self._color = QtGui.QColor() def choose_color(self): color = QtWidgets.QColorDialog.getColor( self._color, self.parentWidget(), "", QtWidgets.QColorDialog.ShowAlphaChannel) if color.isValid(): self.set_color(color) def get_color(self): return self._color @QtCore.Slot(QtGui.QColor) def set_color(self, color): if color != self._color: self._color = color self.colorChanged.emit(self._color) pixmap = QtGui.QPixmap(self.iconSize()) pixmap.fill(color) self.setIcon(QtGui.QIcon(pixmap)) color = QtCore.Property(QtGui.QColor, get_color, set_color)
class FormTabWidget(QtWidgets.QWidget): update_buttons = QtCore.Signal() def __init__(self, datalist, comment="", parent=None): QtWidgets.QWidget.__init__(self, parent) layout = QtWidgets.QVBoxLayout() self.tabwidget = QtWidgets.QTabWidget() layout.addWidget(self.tabwidget) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.widgetlist = [] for data, title, comment in datalist: if len(data[0]) == 3: widget = FormComboWidget(data, comment=comment, parent=self) else: widget = FormWidget(data, with_margin=True, comment=comment, parent=self) index = self.tabwidget.addTab(widget, title) self.tabwidget.setTabToolTip(index, comment) self.widgetlist.append(widget) def setup(self): for widget in self.widgetlist: widget.setup() def get(self): return [widget.get() for widget in self.widgetlist]
class FormComboWidget(QtWidgets.QWidget): update_buttons = QtCore.Signal() def __init__(self, datalist, comment="", parent=None): QtWidgets.QWidget.__init__(self, parent) layout = QtWidgets.QVBoxLayout() self.setLayout(layout) self.combobox = QtWidgets.QComboBox() layout.addWidget(self.combobox) self.stackwidget = QtWidgets.QStackedWidget(self) layout.addWidget(self.stackwidget) self.combobox.currentIndexChanged.connect( self.stackwidget.setCurrentIndex) self.widgetlist = [] for data, title, comment in datalist: self.combobox.addItem(title) widget = FormWidget(data, comment=comment, parent=self) self.stackwidget.addWidget(widget) self.widgetlist.append(widget) def setup(self): for widget in self.widgetlist: widget.setup() def get(self): return [widget.get() for widget in self.widgetlist]
class DimensionWidget(QW.QWidget): """ Widget to select dimensions """ #: Signal emitted when value is changed valueChanged = QtCore.Signal(int) def __init__(self, dimension): """ Construct the widget Args: dimension: xarray.DataArray """ super().__init__() main_layout = QW.QHBoxLayout(self) #: The dimension represented by this widget self.dimension = dimension self.title = QW.QLabel(dimension.name) self.textbox = QW.QLineEdit() self.slider = QW.QSlider(orientation=QtCore.Qt.Horizontal) self.slider.setMinimum(0) self.slider.setMaximum(dimension.size - 1) self.slider.valueChanged.connect(self._update_from_slider) self.textbox.returnPressed.connect(self._update_from_value) self.slider.setValue(0) self.textbox.setText(str(self.dimension[0].values)) main_layout.addWidget(self.title) main_layout.addWidget(self.textbox) main_layout.addWidget(self.slider) def _update_from_value(self): value = self.textbox.text() value = numpy.asscalar(numpy.array(value, dtype=self.dimension.dtype)) index = self.dimension.to_index().get_loc(value, method='nearest') self.slider.setValue(index) def _update_from_slider(self, value): self.textbox.setText(str(self.dimension[value].values)) self.valueChanged.emit(value) def value(self): """ The current slider index """ return self.slider.value()
class Teleporter(QtCore.QObject): name_doc_escape = QtCore.Signal(str, dict, object)
class FormWidget(QtWidgets.QWidget): update_buttons = QtCore.Signal() def __init__(self, data, comment="", with_margin=False, parent=None): """ Parameters ---------- data : list of (label, value) pairs The data to be edited in the form. comment : str, optional with_margin : bool, optional, default: False If False, the form elements reach to the border of the widget. This is the desired behavior if the FormWidget is used as a widget alongside with other widgets such as a QComboBox, which also do not have a margin around them. However, a margin can be desired if the FormWidget is the only widget within a container, e.g. a tab in a QTabWidget. parent : QWidget or None The parent widget. """ QtWidgets.QWidget.__init__(self, parent) self.data = copy.deepcopy(data) self.widgets = [] self.formlayout = QtWidgets.QFormLayout(self) if not with_margin: self.formlayout.setContentsMargins(0, 0, 0, 0) if comment: self.formlayout.addRow(QtWidgets.QLabel(comment)) self.formlayout.addRow(QtWidgets.QLabel(" ")) def get_dialog(self): """Return FormDialog instance""" dialog = self.parent() while not isinstance(dialog, QtWidgets.QDialog): dialog = dialog.parent() return dialog def setup(self): for label, value in self.data: if label is None and value is None: # Separator: (None, None) self.formlayout.addRow(QtWidgets.QLabel(" "), QtWidgets.QLabel(" ")) self.widgets.append(None) continue elif label is None: # Comment self.formlayout.addRow(QtWidgets.QLabel(value)) self.widgets.append(None) continue elif tuple_to_qfont(value) is not None: field = FontLayout(value, self) elif (label.lower() not in BLACKLIST and mcolors.is_color_like(value)): field = ColorLayout(to_qcolor(value), self) elif isinstance(value, str): field = QtWidgets.QLineEdit(value, self) elif isinstance(value, (list, tuple)): if isinstance(value, tuple): value = list(value) # Note: get() below checks the type of value[0] in self.data so # it is essential that value gets modified in-place. # This means that the code is actually broken in the case where # value is a tuple, but fortunately we always pass a list... selindex = value.pop(0) field = QtWidgets.QComboBox(self) if isinstance(value[0], (list, tuple)): keys = [key for key, _val in value] value = [val for _key, val in value] else: keys = value field.addItems(value) if selindex in value: selindex = value.index(selindex) elif selindex in keys: selindex = keys.index(selindex) elif not isinstance(selindex, Integral): _log.warning( "index '%s' is invalid (label: %s, value: %s)", selindex, label, value) selindex = 0 field.setCurrentIndex(selindex) elif isinstance(value, bool): field = QtWidgets.QCheckBox(self) if value: field.setCheckState(QtCore.Qt.Checked) else: field.setCheckState(QtCore.Qt.Unchecked) elif isinstance(value, Integral): field = QtWidgets.QSpinBox(self) field.setRange(-10**9, 10**9) field.setValue(value) elif isinstance(value, Real): field = QtWidgets.QLineEdit(repr(value), self) field.setCursorPosition(0) field.setValidator(QtGui.QDoubleValidator(field)) field.validator().setLocale(QtCore.QLocale("C")) dialog = self.get_dialog() dialog.register_float_field(field) field.textChanged.connect(lambda text: dialog.update_buttons()) elif isinstance(value, datetime.datetime): field = QtWidgets.QDateTimeEdit(self) field.setDateTime(value) elif isinstance(value, datetime.date): field = QtWidgets.QDateEdit(self) field.setDate(value) else: field = QtWidgets.QLineEdit(repr(value), self) self.formlayout.addRow(label, field) self.widgets.append(field) def get(self): valuelist = [] for index, (label, value) in enumerate(self.data): field = self.widgets[index] if label is None: # Separator / Comment continue elif tuple_to_qfont(value) is not None: value = field.get_font() elif isinstance(value, str) or mcolors.is_color_like(value): value = str(field.text()) elif isinstance(value, (list, tuple)): index = int(field.currentIndex()) if isinstance(value[0], (list, tuple)): value = value[index][0] else: value = value[index] elif isinstance(value, bool): value = field.checkState() == QtCore.Qt.Checked elif isinstance(value, Integral): value = int(field.value()) elif isinstance(value, Real): value = float(str(field.text())) elif isinstance(value, datetime.datetime): value = field.dateTime().toPyDateTime() elif isinstance(value, datetime.date): value = field.date().toPyDate() else: value = eval(str(field.text())) valuelist.append(value) return valuelist
class FormWidget(QtWidgets.QWidget): update_buttons = QtCore.Signal() def __init__(self, data, comment="", parent=None): QtWidgets.QWidget.__init__(self, parent) from copy import deepcopy self.data = deepcopy(data) self.widgets = [] self.formlayout = QtWidgets.QFormLayout(self) if comment: self.formlayout.addRow(QtWidgets.QLabel(comment)) self.formlayout.addRow(QtWidgets.QLabel(" ")) if DEBUG: print("\n" + ("*" * 80)) print("DATA:", self.data) print("*" * 80) print("COMMENT:", comment) print("*" * 80) def get_dialog(self): """Return FormDialog instance""" dialog = self.parent() while not isinstance(dialog, QtWidgets.QDialog): dialog = dialog.parent() return dialog def setup(self): for label, value in self.data: if DEBUG: print("value:", value) if label is None and value is None: # Separator: (None, None) self.formlayout.addRow(QtWidgets.QLabel(" "), QtWidgets.QLabel(" ")) self.widgets.append(None) continue elif label is None: # Comment self.formlayout.addRow(QtWidgets.QLabel(value)) self.widgets.append(None) continue elif tuple_to_qfont(value) is not None: field = FontLayout(value, self) elif label.lower() not in BLACKLIST and is_color_like(value): field = ColorLayout(to_qcolor(value), self) elif isinstance(value, six.string_types): field = QtWidgets.QLineEdit(value, self) elif isinstance(value, (list, tuple)): if isinstance(value, tuple): value = list(value) selindex = value.pop(0) field = QtWidgets.QComboBox(self) if isinstance(value[0], (list, tuple)): keys = [key for key, _val in value] value = [val for _key, val in value] else: keys = value field.addItems(value) if selindex in value: selindex = value.index(selindex) elif selindex in keys: selindex = keys.index(selindex) elif not isinstance(selindex, int): print("Warning: '%s' index is invalid (label: " "%s, value: %s)" % (selindex, label, value), file=STDERR) selindex = 0 field.setCurrentIndex(selindex) elif isinstance(value, bool): field = QtWidgets.QCheckBox(self) if value: field.setCheckState(QtCore.Qt.Checked) else: field.setCheckState(QtCore.Qt.Unchecked) elif isinstance(value, float): field = QtWidgets.QLineEdit(repr(value), self) field.setCursorPosition(0) field.setValidator(QtGui.QDoubleValidator(field)) field.validator().setLocale(QtCore.QLocale("C")) dialog = self.get_dialog() dialog.register_float_field(field) field.textChanged.connect(lambda text: dialog.update_buttons()) elif isinstance(value, int): field = QtWidgets.QSpinBox(self) field.setRange(-1e9, 1e9) field.setValue(value) elif isinstance(value, datetime.datetime): field = QtWidgets.QDateTimeEdit(self) field.setDateTime(value) elif isinstance(value, datetime.date): field = QtWidgets.QDateEdit(self) field.setDate(value) else: field = QtWidgets.QLineEdit(repr(value), self) self.formlayout.addRow(label, field) self.widgets.append(field) def get(self): valuelist = [] for index, (label, value) in enumerate(self.data): field = self.widgets[index] if label is None: # Separator / Comment continue elif tuple_to_qfont(value) is not None: value = field.get_font() elif isinstance(value, six.string_types) or is_color_like(value): value = six.text_type(field.text()) elif isinstance(value, (list, tuple)): index = int(field.currentIndex()) if isinstance(value[0], (list, tuple)): value = value[index][0] else: value = value[index] elif isinstance(value, bool): value = field.checkState() == QtCore.Qt.Checked elif isinstance(value, float): value = float(str(field.text())) elif isinstance(value, int): value = int(field.value()) elif isinstance(value, datetime.datetime): value = field.dateTime().toPyDateTime() elif isinstance(value, datetime.date): value = field.date().toPyDate() else: value = eval(str(field.text())) valuelist.append(value) return valuelist
class FormWidget(QtWidgets.QWidget): update_buttons = QtCore.Signal() def __init__(self, data, comment="", parent=None): QtWidgets.QWidget.__init__(self, parent) self.data = copy.deepcopy(data) self.widgets = [] self.formlayout = QtWidgets.QFormLayout(self) if comment: self.formlayout.addRow(QtWidgets.QLabel(comment)) self.formlayout.addRow(QtWidgets.QLabel(" ")) def get_dialog(self): """Return FormDialog instance""" dialog = self.parent() while not isinstance(dialog, QtWidgets.QDialog): dialog = dialog.parent() return dialog def setup(self): for label, value in self.data: if label is None and value is None: # Separator: (None, None) self.formlayout.addRow(QtWidgets.QLabel(" "), QtWidgets.QLabel(" ")) self.widgets.append(None) continue elif label is None: # Comment self.formlayout.addRow(QtWidgets.QLabel(value)) self.widgets.append(None) continue elif tuple_to_qfont(value) is not None: field = FontLayout(value, self) elif (label.lower() not in BLACKLIST and mcolors.is_color_like(value)): field = ColorLayout(to_qcolor(value), self) elif isinstance(value, str): field = QtWidgets.QLineEdit(value, self) elif isinstance(value, (list, tuple)): if isinstance(value, tuple): value = list(value) # Note: get() below checks the type of value[0] in self.data so # it is essential that value gets modified in-place. # This means that the code is actually broken in the case where # value is a tuple, but fortunately we always pass a list... selindex = value.pop(0) field = QtWidgets.QComboBox(self) if isinstance(value[0], (list, tuple)): keys = [key for key, _val in value] value = [val for _key, val in value] else: keys = value field.addItems(value) if selindex in value: selindex = value.index(selindex) elif selindex in keys: selindex = keys.index(selindex) elif not isinstance(selindex, Integral): _log.warning( "index '%s' is invalid (label: %s, value: %s)", selindex, label, value) selindex = 0 field.setCurrentIndex(selindex) elif isinstance(value, bool): field = QtWidgets.QCheckBox(self) if value: field.setCheckState(QtCore.Qt.Checked) else: field.setCheckState(QtCore.Qt.Unchecked) elif isinstance(value, Integral): field = QtWidgets.QSpinBox(self) field.setRange(-1e9, 1e9) field.setValue(value) elif isinstance(value, Real): field = QtWidgets.QLineEdit(repr(value), self) field.setCursorPosition(0) field.setValidator(QtGui.QDoubleValidator(field)) field.validator().setLocale(QtCore.QLocale("C")) dialog = self.get_dialog() dialog.register_float_field(field) field.textChanged.connect(lambda text: dialog.update_buttons()) elif isinstance(value, datetime.datetime): field = QtWidgets.QDateTimeEdit(self) field.setDateTime(value) elif isinstance(value, datetime.date): field = QtWidgets.QDateEdit(self) field.setDate(value) else: field = QtWidgets.QLineEdit(repr(value), self) self.formlayout.addRow(label, field) self.widgets.append(field) def get(self): valuelist = [] for index, (label, value) in enumerate(self.data): field = self.widgets[index] if label is None: # Separator / Comment continue elif tuple_to_qfont(value) is not None: value = field.get_font() elif isinstance(value, str) or mcolors.is_color_like(value): value = str(field.text()) elif isinstance(value, (list, tuple)): index = int(field.currentIndex()) if isinstance(value[0], (list, tuple)): value = value[index][0] else: value = value[index] elif isinstance(value, bool): value = field.checkState() == QtCore.Qt.Checked elif isinstance(value, Integral): value = int(field.value()) elif isinstance(value, Real): value = float(str(field.text())) elif isinstance(value, datetime.datetime): value = field.dateTime().toPyDateTime() elif isinstance(value, datetime.date): value = field.date().toPyDate() else: value = eval(str(field.text())) valuelist.append(value) return valuelist
class ColorBarWidget(QW.QWidget): """ Contains the colour bar and controls to change bounds """ valueChanged = QtCore.Signal(float, float) def __init__(self): super().__init__() main_layout = QW.QVBoxLayout(self) figure = Figure() figure.set_frameon(False) self.canvas = FigureCanvas(figure) self.canvas.setStyleSheet("background-color:transparent;") self.axis = self.canvas.figure.add_axes([0, 0.05, 0.2, 0.9]) self.upperTextBox = QW.QLineEdit() self.lowerTextBox = QW.QLineEdit() main_layout.addWidget(self.upperTextBox) main_layout.addWidget(self.canvas) main_layout.addWidget(self.lowerTextBox) self.upperTextBox.returnPressed.connect(self._update_bounds) self.lowerTextBox.returnPressed.connect(self._update_bounds) #: Colour bar limits self.bounds = [numpy.nan, numpy.nan] self.setFixedWidth(80) def setBounds(self, bounds): self.bounds = bounds self.lowerTextBox.setText("%.2e" % bounds[0]) self.upperTextBox.setText("%.2e" % bounds[1]) def redraw(self, plot): """ Redraw the colour bar """ self.axis.clear() if plot is not None: plt.colorbar(plot, cax=self.axis) self.canvas.draw() def get_plot_args(self): kwargs = {} if self.bounds[0] < 0 < self.bounds[1]: kwargs['vmax'] = numpy.abs(self.bounds).max() kwargs['vmin'] = -kwargs['vmax'] else: kwargs['vmin'] = self.bounds[0] kwargs['vmax'] = self.bounds[1] return kwargs def _update_bounds(self): values = [self.lowerTextBox.text(), self.upperTextBox.text()] self.bounds = numpy.array(values, dtype=self.bounds.dtype) self.valueChanged.emit(self.bounds[0], self.bounds[1])
class NavigationToolbar_(NavigationToolbar2, QtWidgets.QToolBar): message = QtCore.Signal(str) def __init__(self, canvas, parent, coordinates=True): """coordinates: should we show the coordinates on the right?""" self.canvas = canvas self.parent = parent self.coordinates = coordinates self._actions = {} """A mapping of toolitem method names to their QActions""" QtWidgets.QToolBar.__init__(self, parent) NavigationToolbar2.__init__(self, canvas) def _icon(self, name, color=None): if is_pyqt5(): name = name.replace('.png', '_large.png') pm = QtGui.QPixmap(os.path.join(self.basedir, name)) if hasattr(pm, 'setDevicePixelRatio'): pm.setDevicePixelRatio(self.canvas._dpi_ratio) if color is not None: mask = pm.createMaskFromColor(QtGui.QColor('black'), QtCore.Qt.MaskOutColor) pm.fill(color) pm.setMask(mask) return QtGui.QIcon(pm) def _init_toolbar(self): self.basedir = str(cbook._get_data_path('images')) background_color = self.palette().color(self.backgroundRole()) foreground_color = self.palette().color(self.foregroundRole()) icon_color = (foreground_color if background_color.value() < 128 else None) for text, tooltip_text, image_file, callback in self.toolitems: if callback not in [ 'zoom', 'pan', 'back', 'forward', 'home', 'configure_subplots' ]: if text is None: #self.addSeparator() pass else: a = self.addAction(QIcon('res\\save_icon.ico'), text, getattr(self, callback)) self._actions[callback] = a if tooltip_text is not None: a.setToolTip(tooltip_text) if text == 'Subplots': a = self.addAction( self._icon("qt4_editor_options.png", icon_color), 'Customize', self.edit_parameters) a.setToolTip('Edit axis, curve and image parameters') # Add the (x, y) location widget at the right side of the toolbar # The stretch factor is 1 which means any resizing of the toolbar # will resize this label instead of the buttons. self.measure_win_A = QtWidgets.QAction(QIcon("res\\m_icon.ico"), "Open Measurement Window", self) self.measure_win_A.triggered.connect(self.parent.Open_measure_window) self.addAction(self.measure_win_A) self.statusLabel = QtWidgets.QLabel("", self) self.statusLabel.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) self.statusLabel.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Ignored)) self.statusLabel.setStyleSheet("QLabel { color : red; }") self.statusLabelAction = self.addWidget(self.statusLabel) font1 = QtGui.QFont("Times", 12) self.statusLabelAction.setVisible(False) self.statusLabel.setFont(font1) if self.coordinates: self.locLabel = QtWidgets.QLabel("", self) self.locLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.locLabel.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Ignored)) self.coordinateLabel = self.addWidget(self.locLabel) newfont = QtGui.QFont("Times", 12) self.coordinateLabel.setVisible(True) self.locLabel.setFont(newfont) @cbook.deprecated("3.1") @property def buttons(self): return {} @cbook.deprecated("3.1") @property def adj_window(self): return None def edit_parameters(self): axes = self.canvas.figure.get_axes() if not axes: QtWidgets.QMessageBox.warning(self.parent, "Error", "There are no axes to edit.") return elif len(axes) == 1: ax, = axes else: titles = [ ax.get_label() or ax.get_title() or " - ".join(filter( None, [ax.get_xlabel(), ax.get_ylabel()])) or f"<anonymous {type(ax).__name__}>" for ax in axes ] duplicate_titles = [ title for title in titles if titles.count(title) > 1 ] for i, ax in enumerate(axes): if titles[i] in duplicate_titles: titles[i] += f" (id: {id(ax):#x})" # Deduplicate titles. item, ok = QtWidgets.QInputDialog.getItem(self.parent, 'Customize', 'Select axes:', titles, 0, False) if not ok: return ax = axes[titles.index(item)] figureoptions.figure_edit(ax, self) def _update_buttons_checked(self): # sync button checkstates to match active mode if 'pan' in self._actions: self._actions['pan'].setChecked(self._active == 'PAN') if 'zoom' in self._actions: self._actions['zoom'].setChecked(self._active == 'ZOOM') def pan(self, *args): super().pan(*args) self._update_buttons_checked() def zoom(self, *args): super().zoom(*args) self._update_buttons_checked() def set_message(self, s): self.message.emit(s) if self.coordinates: s = s.replace("x=", "t=") self.locLabel.setText(s.rstrip(' y=') + " s") def set_cursor(self, cursor): self.canvas.setCursor(cursord[cursor]) def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height y1 = height - y1 y0 = height - y0 rect = [int(val) for val in (x0, y0, x1 - x0, y1 - y0)] self.canvas.drawRectangle(rect) def remove_rubberband(self): self.canvas.drawRectangle(None) def configure_subplots(self): image = str(cbook._get_data_path('images/matplotlib.png')) dia = SubplotToolQt(self.canvas.figure, self.canvas.parent()) dia.setWindowIcon(QtGui.QIcon(image)) dia.exec_() def save_figure(self, *args): filetypes = self.canvas.get_supported_filetypes_grouped() sorted_filetypes = sorted(filetypes.items()) default_filetype = self.canvas.get_default_filetype() startpath = os.path.expanduser( matplotlib.rcParams['savefig.directory']) start = os.path.join(startpath, self.canvas.get_default_filename()) filters = [] selectedFilter = None for name, exts in sorted_filetypes: exts_list = " ".join(['*.%s' % ext for ext in exts]) filter = '%s (%s)' % (name, exts_list) if default_filetype in exts: selectedFilter = filter filters.append(filter) filters = ';;'.join(filters) fname, filter = _getSaveFileName(self.canvas.parent(), "Choose a filename to save to", start, filters, selectedFilter) if fname: # Save dir for next time, unless empty str (i.e., use cwd). if startpath != "": matplotlib.rcParams['savefig.directory'] = ( os.path.dirname(fname)) try: self.canvas.figure.savefig(fname) except Exception as e: QtWidgets.QMessageBox.critical(self, "Error saving file", str(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton) def set_history_buttons(self): can_backward = self._nav_stack._pos > 0 can_forward = self._nav_stack._pos < len(self._nav_stack._elements) - 1 if 'back' in self._actions: self._actions['back'].setEnabled(can_backward) if 'forward' in self._actions: self._actions['forward'].setEnabled(can_forward)
class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar): message = QtCore.Signal(str) def __init__(self, canvas, parent, coordinates=True): """ coordinates: should we show the coordinates on the right? """ self.canvas = canvas self.parent = parent self.coordinates = coordinates self._actions = {} """A mapping of toolitem method names to their QActions""" QtWidgets.QToolBar.__init__(self, parent) NavigationToolbar2.__init__(self, canvas) def _icon(self, name): return QtGui.QIcon(os.path.join(self.basedir, name)) def _init_toolbar(self): self.basedir = os.path.join(matplotlib.rcParams['datapath'], 'images') for text, tooltip_text, image_file, callback in self.toolitems: if text is None: self.addSeparator() else: a = self.addAction(self._icon(image_file + '.png'), text, getattr(self, callback)) self._actions[callback] = a if callback in ['zoom', 'pan']: a.setCheckable(True) if tooltip_text is not None: a.setToolTip(tooltip_text) if figureoptions is not None: a = self.addAction(self._icon("qt4_editor_options.png"), 'Customize', self.edit_parameters) a.setToolTip('Edit axis, curve and image parameters') self.buttons = {} # Add the x,y location widget at the right side of the toolbar # The stretch factor is 1 which means any resizing of the toolbar # will resize this label instead of the buttons. if self.coordinates: self.locLabel = QtWidgets.QLabel("", self) self.locLabel.setAlignment( QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) self.locLabel.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Ignored)) labelAction = self.addWidget(self.locLabel) labelAction.setVisible(True) # reference holder for subplots_adjust window self.adj_window = None if figureoptions is not None: def edit_parameters(self): allaxes = self.canvas.figure.get_axes() if not allaxes: QtWidgets.QMessageBox.warning( self.parent, "Error", "There are no axes to edit.") return if len(allaxes) == 1: axes = allaxes[0] else: titles = [] for axes in allaxes: name = (axes.get_title() or " - ".join(filter(None, [axes.get_xlabel(), axes.get_ylabel()])) or "<anonymous {} (id: {:#x})>".format( type(axes).__name__, id(axes))) titles.append(name) item, ok = QtWidgets.QInputDialog.getItem( self.parent, 'Customize', 'Select axes:', titles, 0, False) if ok: axes = allaxes[titles.index(six.text_type(item))] else: return figureoptions.figure_edit(axes, self) def _update_buttons_checked(self): # sync button checkstates to match active mode self._actions['pan'].setChecked(self._active == 'PAN') self._actions['zoom'].setChecked(self._active == 'ZOOM') def pan(self, *args): super(NavigationToolbar2QT, self).pan(*args) self._update_buttons_checked() def zoom(self, *args): super(NavigationToolbar2QT, self).zoom(*args) self._update_buttons_checked() def dynamic_update(self): self.canvas.draw_idle() def set_message(self, s): self.message.emit(s) if self.coordinates: self.locLabel.setText(s) def set_cursor(self, cursor): if DEBUG: print('Set cursor', cursor) self.canvas.setCursor(cursord[cursor]) def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height y1 = height - y1 y0 = height - y0 w = abs(x1 - x0) h = abs(y1 - y0) rect = [int(val)for val in (min(x0, x1), min(y0, y1), w, h)] self.canvas.drawRectangle(rect) def remove_rubberband(self): self.canvas.drawRectangle(None) def configure_subplots(self): image = os.path.join(matplotlib.rcParams['datapath'], 'images', 'matplotlib.png') dia = SubplotToolQt(self.canvas.figure, self.parent) dia.setWindowIcon(QtGui.QIcon(image)) dia.exec_() def save_figure(self, *args): filetypes = self.canvas.get_supported_filetypes_grouped() sorted_filetypes = list(six.iteritems(filetypes)) sorted_filetypes.sort() default_filetype = self.canvas.get_default_filetype() startpath = matplotlib.rcParams.get('savefig.directory', '') startpath = os.path.expanduser(startpath) start = os.path.join(startpath, self.canvas.get_default_filename()) filters = [] selectedFilter = None for name, exts in sorted_filetypes: exts_list = " ".join(['*.%s' % ext for ext in exts]) filter = '%s (%s)' % (name, exts_list) if default_filetype in exts: selectedFilter = filter filters.append(filter) filters = ';;'.join(filters) fname, filter = _getSaveFileName(self.parent, "Choose a filename to save to", start, filters, selectedFilter) if fname: if startpath == '': # explicitly missing key or empty str signals to use cwd matplotlib.rcParams['savefig.directory'] = startpath else: # save dir for next time savefig_dir = os.path.dirname(six.text_type(fname)) matplotlib.rcParams['savefig.directory'] = savefig_dir try: self.canvas.print_figure(six.text_type(fname)) except Exception as e: QtWidgets.QMessageBox.critical( self, "Error saving file", six.text_type(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton)
class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar): message = QtCore.Signal(str) def __init__(self, canvas, parent, coordinates=True): """ coordinates: should we show the coordinates on the right? """ self.canvas = canvas self.parent = parent self.coordinates = coordinates self._actions = {} """A mapping of toolitem method names to their QActions""" QtWidgets.QToolBar.__init__(self, parent) NavigationToolbar2.__init__(self, canvas) def _icon(self, name): if is_pyqt5(): name = name.replace('.png', '_large.png') pm = QtGui.QPixmap(os.path.join(self.basedir, name)) if hasattr(pm, 'setDevicePixelRatio'): pm.setDevicePixelRatio(self.canvas._dpi_ratio) return QtGui.QIcon(pm) def _init_toolbar(self): self.basedir = os.path.join(matplotlib.rcParams['datapath'], 'images') for text, tooltip_text, image_file, callback in self.toolitems: if text is None: self.addSeparator() else: a = self.addAction(self._icon(image_file + '.png'), text, getattr(self, callback)) self._actions[callback] = a if callback in ['zoom', 'pan']: a.setCheckable(True) if tooltip_text is not None: a.setToolTip(tooltip_text) if text == 'Subplots': a = self.addAction(self._icon("qt4_editor_options.png"), 'Customize', self.edit_parameters) a.setToolTip('Edit axis, curve and image parameters') self.buttons = {} # Add the x,y location widget at the right side of the toolbar # The stretch factor is 1 which means any resizing of the toolbar # will resize this label instead of the buttons. if self.coordinates: self.locLabel = QtWidgets.QLabel("", self) self.locLabel.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) self.locLabel.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Ignored)) labelAction = self.addWidget(self.locLabel) labelAction.setVisible(True) # reference holder for subplots_adjust window self.adj_window = None # Esthetic adjustments - we need to set these explicitly in PyQt5 # otherwise the layout looks different - but we don't want to set it if # not using HiDPI icons otherwise they look worse than before. if is_pyqt5(): self.setIconSize(QtCore.QSize(24, 24)) self.layout().setSpacing(12) if is_pyqt5(): # For some reason, self.setMinimumHeight doesn't seem to carry over to # the actual sizeHint, so override it instead in order to make the # aesthetic adjustments noted above. def sizeHint(self): size = super(NavigationToolbar2QT, self).sizeHint() size.setHeight(max(48, size.height())) return size def edit_parameters(self): allaxes = self.canvas.figure.get_axes() if not allaxes: QtWidgets.QMessageBox.warning(self.parent, "Error", "There are no axes to edit.") return elif len(allaxes) == 1: axes, = allaxes else: titles = [] for axes in allaxes: name = (axes.get_title() or " - ".join( filter(None, [axes.get_xlabel(), axes.get_ylabel()])) or "<anonymous {} (id: {:#x})>".format( type(axes).__name__, id(axes))) titles.append(name) item, ok = QtWidgets.QInputDialog.getItem(self.parent, 'Customize', 'Select axes:', titles, 0, False) if ok: axes = allaxes[titles.index(six.text_type(item))] else: return figureoptions.figure_edit(axes, self) def _update_buttons_checked(self): # sync button checkstates to match active mode self._actions['pan'].setChecked(self._active == 'PAN') self._actions['zoom'].setChecked(self._active == 'ZOOM') def pan(self, *args): super(NavigationToolbar2QT, self).pan(*args) self._update_buttons_checked() def zoom(self, *args): super(NavigationToolbar2QT, self).zoom(*args) self._update_buttons_checked() def set_message(self, s): self.message.emit(s) if self.coordinates: self.locLabel.setText(s) def set_cursor(self, cursor): self.canvas.setCursor(cursord[cursor]) def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height y1 = height - y1 y0 = height - y0 rect = [int(val) for val in (x0, y0, x1 - x0, y1 - y0)] self.canvas.drawRectangle(rect) def remove_rubberband(self): self.canvas.drawRectangle(None) def configure_subplots(self): image = os.path.join(matplotlib.rcParams['datapath'], 'images', 'matplotlib.png') dia = SubplotToolQt(self.canvas.figure, self.parent) dia.setWindowIcon(QtGui.QIcon(image)) dia.exec_() def save_figure(self, *args): filetypes = self.canvas.get_supported_filetypes_grouped() sorted_filetypes = sorted(six.iteritems(filetypes)) default_filetype = self.canvas.get_default_filetype() startpath = os.path.expanduser( matplotlib.rcParams['savefig.directory']) start = os.path.join(startpath, self.canvas.get_default_filename()) filters = [] selectedFilter = None for name, exts in sorted_filetypes: exts_list = " ".join(['*.%s' % ext for ext in exts]) filter = '%s (%s)' % (name, exts_list) if default_filetype in exts: selectedFilter = filter filters.append(filter) filters = ';;'.join(filters) fname, filter = _getSaveFileName(self.parent, "Choose a filename to save to", start, filters, selectedFilter) 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))) try: self.canvas.figure.savefig(six.text_type(fname)) except Exception as e: QtWidgets.QMessageBox.critical(self, "Error saving file", six.text_type(e), QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton)
class MainWindow(QtWidgets.QMainWindow): closing = QtCore.Signal() def closeEvent(self, event): self.closing.emit() QtWidgets.QMainWindow.closeEvent(self, event)