From a3b049f3427dee9f9d51cdf2a43b1a4d378bc6e6 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Mon, 4 Dec 2017 19:32:53 -0500 Subject: [PATCH 1/3] MEP22 implementation for QT backend New Toolbar and QT specific tools --- doc/users/next_whats_new/qt-toolmanager.rst | 17 ++ .../user_interfaces/toolmanager_sgskip.py | 4 +- lib/matplotlib/backends/backend_qt5.py | 177 +++++++++++++++++- 3 files changed, 191 insertions(+), 7 deletions(-) create mode 100644 doc/users/next_whats_new/qt-toolmanager.rst diff --git a/doc/users/next_whats_new/qt-toolmanager.rst b/doc/users/next_whats_new/qt-toolmanager.rst new file mode 100644 index 000000000000..2304b3f8efef --- /dev/null +++ b/doc/users/next_whats_new/qt-toolmanager.rst @@ -0,0 +1,17 @@ +Added support for QT in new ToolManager +======================================= + +Now it is possible to use the ToolManager with Qt5 +For example + + import matplotlib + + matplotlib.use('QT5AGG') + matplotlib.rcParams['toolbar'] = 'toolmanager' + import matplotlib.pyplot as plt + + plt.plot([1,2,3]) + plt.show() + +The main example `examples/user_interfaces/toolmanager_sgskip.py` shows more +details, just adjust the header to use QT instead of GTK3 diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 0c77fe55e699..247997f6e2e2 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -16,11 +16,13 @@ from __future__ import print_function import matplotlib +# Change to the desired backend matplotlib.use('GTK3Cairo') +# matplotlib.use('TkAgg') +# matplotlib.use('QT5Agg') matplotlib.rcParams['toolbar'] = 'toolmanager' import matplotlib.pyplot as plt from matplotlib.backend_tools import ToolBase, ToolToggleBase -from gi.repository import Gtk, Gdk class ListTools(ToolBase): diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 9110cfce440b..6c665836533d 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -14,10 +14,12 @@ from matplotlib._pylab_helpers import Gcf from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2, - TimerBase, cursors) + 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 +from matplotlib.backend_managers import ToolManager +from matplotlib import backend_tools from .qt_compat import ( QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API) @@ -489,14 +491,23 @@ def __init__(self, canvas, num): self.window._destroying = False - # add text label to status bar - self.statusbar_label = QtWidgets.QLabel() - self.window.statusBar().addWidget(self.statusbar_label) - + self.toolmanager = self._get_toolmanager() self.toolbar = self._get_toolbar(self.canvas, self.window) + self.statusbar = None + + if self.toolmanager: + backend_tools.add_tools_to_manager(self.toolmanager) + if self.toolbar: + backend_tools.add_tools_to_container(self.toolbar) + self.statusbar = StatusbarQt(self.window, self.toolmanager) + if self.toolbar is not None: self.window.addToolBar(self.toolbar) - self.toolbar.message.connect(self.statusbar_label.setText) + if not self.toolmanager: + # add text label to status bar + statusbar_label = QtWidgets.QLabel() + self.window.statusBar().addWidget(statusbar_label) + self.toolbar.message.connect(statusbar_label.setText) tbs_height = self.toolbar.sizeHint().height() else: tbs_height = 0 @@ -545,10 +556,19 @@ def _get_toolbar(self, canvas, parent): # attrs are set if matplotlib.rcParams['toolbar'] == 'toolbar2': toolbar = NavigationToolbar2QT(canvas, parent, False) + elif matplotlib.rcParams['toolbar'] == 'toolmanager': + toolbar = ToolbarQt(self.toolmanager, self.window) else: toolbar = None return toolbar + def _get_toolmanager(self): + if matplotlib.rcParams['toolbar'] == 'toolmanager': + toolmanager = ToolManager(self.canvas.figure) + else: + toolmanager = None + return toolmanager + def resize(self, width, height): 'set the canvas size in pixels' self.window.resize(width, height + self._status_and_tool_height) @@ -818,6 +838,151 @@ def _reset(self): self._widgets[attr].setValue(value) +class ToolbarQt(ToolContainerBase, QtWidgets.QToolBar): + def __init__(self, toolmanager, parent): + ToolContainerBase.__init__(self, toolmanager) + QtWidgets.QToolBar.__init__(self, parent) + self._toolitems = {} + self._groups = {} + self._last = None + + @property + def _icon_extension(self): + if is_pyqt5(): + return '_large.png' + return '.png' + + def add_toolitem( + self, name, group, position, image_file, description, toggle): + + button = QtWidgets.QToolButton(self) + button.setIcon(self._icon(image_file)) + button.setText(name) + if description: + button.setToolTip(description) + + def handler(): + self.trigger_tool(name) + if toggle: + button.setCheckable(True) + button.toggled.connect(handler) + else: + button.clicked.connect(handler) + + self._last = button + self._toolitems.setdefault(name, []) + self._add_to_group(group, name, button, position) + self._toolitems[name].append((button, handler)) + + def _add_to_group(self, group, name, button, position): + gr = self._groups.get(group, []) + if not gr: + sep = self.addSeparator() + gr.append(sep) + before = gr[position] + widget = self.insertWidget(before, button) + gr.insert(position, widget) + self._groups[group] = gr + + def _icon(self, name): + pm = QtGui.QPixmap(name) + if hasattr(pm, 'setDevicePixelRatio'): + pm.setDevicePixelRatio(self.canvas._dpi_ratio) + return QtGui.QIcon(pm) + + def toggle_toolitem(self, name, toggled): + if name not in self._toolitems: + return + for button, handler in self._toolitems[name]: + button.toggled.disconnect(handler) + button.setChecked(toggled) + button.toggled.connect(handler) + + def remove_toolitem(self, name): + for button, handler in self._toolitems[name]: + button.setParent(None) + del self._toolitems[name] + + +class StatusbarQt(StatusbarBase, QtWidgets.QLabel): + def __init__(self, window, *args, **kwargs): + StatusbarBase.__init__(self, *args, **kwargs) + QtWidgets.QLabel.__init__(self) + window.statusBar().addWidget(self) + + def set_message(self, s): + self.setText(s) + + +class ConfigureSubplotsQt(backend_tools.ConfigureSubplotsBase): + def trigger(self, *args): + image = os.path.join(matplotlib.rcParams['datapath'], + 'images', 'matplotlib.png') + parent = self.canvas.manager.window + dia = SubplotToolQt(self.figure, parent) + dia.setWindowIcon(QtGui.QIcon(image)) + dia.exec_() + + +class SaveFigureQt(backend_tools.SaveFigureBase): + def trigger(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) + + parent = self.canvas.manager.window + fname, filter = _getSaveFileName(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 SetCursorQt(backend_tools.SetCursorBase): + def set_cursor(self, cursor): + self.canvas.setCursor(cursord[cursor]) + + +class RubberbandQt(backend_tools.RubberbandBase): + def draw_rubberband(self, 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) + + +backend_tools.ToolSaveFigure = SaveFigureQt +backend_tools.ToolConfigureSubplots = ConfigureSubplotsQt +backend_tools.ToolSetCursor = SetCursorQt +backend_tools.ToolRubberband = RubberbandQt + + def error_msg_qt(msg, parent=None): if not isinstance(msg, six.string_types): msg = ','.join(map(str, msg)) From 958b512aac92ef78c4388fe384b2bf23bb96bc5d Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Tue, 5 Dec 2017 16:23:33 -0500 Subject: [PATCH 2/3] fix qt5 --- lib/matplotlib/backends/backend_qt5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 6c665836533d..fc577dd71def 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -887,7 +887,7 @@ def _add_to_group(self, group, name, button, position): def _icon(self, name): pm = QtGui.QPixmap(name) if hasattr(pm, 'setDevicePixelRatio'): - pm.setDevicePixelRatio(self.canvas._dpi_ratio) + pm.setDevicePixelRatio(self.toolmanager.canvas._dpi_ratio) return QtGui.QIcon(pm) def toggle_toolitem(self, name, toggled): From 943c40dfc20aa2249428804c94c00caa06b35594 Mon Sep 17 00:00:00 2001 From: Federico Ariza Date: Thu, 1 Feb 2018 08:38:29 -0500 Subject: [PATCH 3/3] warning in whats new --- doc/users/next_whats_new/qt-toolmanager.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/users/next_whats_new/qt-toolmanager.rst b/doc/users/next_whats_new/qt-toolmanager.rst index 2304b3f8efef..db61617dca47 100644 --- a/doc/users/next_whats_new/qt-toolmanager.rst +++ b/doc/users/next_whats_new/qt-toolmanager.rst @@ -13,5 +13,8 @@ For example plt.plot([1,2,3]) plt.show() + +Treat the new Tool classes experimental for now, the API will likely change and perhaps the rcParam as well + The main example `examples/user_interfaces/toolmanager_sgskip.py` shows more details, just adjust the header to use QT instead of GTK3 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy