/
matplotlib_3.1.1.1.diff
343 lines (310 loc) · 13.7 KB
/
matplotlib_3.1.1.1.diff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
diff --git lib/matplotlib/backends/backend_qt5.py lib/matplotlib/backends/backend_qt5.py
index b4562db7f..77caced86 100644
--- lib/matplotlib/backends/backend_qt5.py
+++ lib/matplotlib/backends/backend_qt5.py
@@ -17,7 +17,7 @@ from matplotlib.backends.qt_editor.formsubplottool import UiSubplotTool
from matplotlib.backend_managers import ToolManager
from .qt_compat import (
- QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, __version__, QT_API)
+ QtCore, QtGui, QtWidgets, _getSaveFileName, is_pyqt5, is_qt5, __version__, QT_API)
backend_version = __version__
@@ -103,9 +103,9 @@ def _create_qApp():
if qApp is None:
app = QtWidgets.QApplication.instance()
- if app is None:
+ if is_pyqt5() and app is None:
# check for DISPLAY env variable on X11 build of Qt
- if is_pyqt5():
+ if is_qt5():
try:
from PyQt5 import QtX11Extras
is_x11_build = True
@@ -128,7 +128,7 @@ def _create_qApp():
else:
qApp = app
- if is_pyqt5():
+ if is_qt5():
try:
qApp.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
except AttributeError:
@@ -137,7 +137,7 @@ def _create_qApp():
def _allow_super_init(__init__):
"""
- Decorator for ``__init__`` to allow ``super().__init__`` on PyQt4/PySide2.
+ Decorator for ``__init__`` to allow ``super().__init__`` on PyQt4/PySide2/PythonQt.
"""
if QT_API == "PyQt5":
@@ -145,8 +145,8 @@ def _allow_super_init(__init__):
return __init__
else:
- # To work around lack of cooperative inheritance in PyQt4, PySide,
- # and PySide2, when calling FigureCanvasQT.__init__, we temporarily
+ # To work around lack of cooperative inheritance in PyQt4, PySide, PySide2
+ # and PythonQt, when calling FigureCanvasQT.__init__, we temporarily
# patch QWidget.__init__ by a cooperative version, that first calls
# QWidget.__init__ with no additional arguments, and then finds the
# next class in the MRO with an __init__ that does support cooperative
@@ -162,7 +162,7 @@ def _allow_super_init(__init__):
next_coop_init = next(
cls for cls in mro[mro.index(QtWidgets.QWidget) + 1:]
if cls.__module__.split(".")[0] not in [
- "PyQt4", "sip", "PySide", "PySide2", "Shiboken"])
+ "PyQt4", "sip", "PySide", "PySide2", "Shiboken", "PythonQt"])
next_coop_init.__init__(self, *args, **kwargs)
@functools.wraps(__init__)
@@ -528,8 +528,16 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
class MainWindow(QtWidgets.QMainWindow):
closing = QtCore.Signal()
+ def __init__(self):
+ QtGui.QMainWindow.__init__(self)
+ self._closeCallbacks = []
+
+ def connectClosing(self, callback):
+ self._closeCallbacks.append(callback)
+
def closeEvent(self, event):
- self.closing.emit()
+ for callback in self._closeCallbacks:
+ callback()
QtWidgets.QMainWindow.closeEvent(self, event)
@@ -552,8 +560,9 @@ class FigureManagerQT(FigureManagerBase):
FigureManagerBase.__init__(self, canvas, num)
self.canvas = canvas
self.window = MainWindow()
- self.window.closing.connect(canvas.close_event)
- self.window.closing.connect(self._widgetclosed)
+ self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
+ self.window.connectClosing(canvas.close_event)
+ self.window.connectClosing(self._widgetclosed)
self.window.setWindowTitle("Figure %d" % num)
image = os.path.join(matplotlib.rcParams['datapath'],
@@ -680,7 +689,7 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
def __init__(self, canvas, parent, coordinates=True):
""" coordinates: should we show the coordinates on the right? """
self.canvas = canvas
- self.parent = parent
+ self._parent = parent
self.coordinates = coordinates
self._actions = {}
"""A mapping of toolitem method names to their QActions"""
@@ -689,7 +698,7 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
NavigationToolbar2.__init__(self, canvas)
def _icon(self, name):
- if is_pyqt5():
+ if is_qt5():
name = name.replace('.png', '_large.png')
pm = QtGui.QPixmap(os.path.join(self.basedir, name))
if hasattr(pm, 'setDevicePixelRatio'):
@@ -731,7 +740,7 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
# 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() and self.canvas._dpi_ratio > 1:
+ if is_qt5() and self.canvas._dpi_ratio > 1:
self.setIconSize(QtCore.QSize(24, 24))
self.layout().setSpacing(12)
@@ -745,12 +754,12 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
def adj_window(self):
return None
- if is_pyqt5():
+ if is_qt5():
# 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().sizeHint()
+ size = QtWidgets.QToolBar.sizeHint(self)
size.setHeight(max(48, size.height()))
return size
@@ -758,7 +767,7 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
axes = self.canvas.figure.get_axes()
if not axes:
QtWidgets.QMessageBox.warning(
- self.parent, "Error", "There are no axes to edit.")
+ self._parent, "Error", "There are no axes to edit.")
return
elif len(axes) == 1:
ax, = axes
@@ -775,7 +784,7 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
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)
+ self._parent, 'Customize', 'Select axes:', titles, 0, False)
if not ok:
return
ax = axes[titles.index(item)]
@@ -1089,11 +1098,13 @@ class _BackendQT5(_Backend):
@staticmethod
def mainloop():
- old_signal = signal.getsignal(signal.SIGINT)
- # allow SIGINT exceptions to close the plot window.
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- try:
- qApp.exec_()
- finally:
- # reset the SIGINT exception handler
- signal.signal(signal.SIGINT, old_signal)
+ # PythonQt is already running the event loop
+ if QT_API != "PythonQt":
+ old_signal = signal.getsignal(signal.SIGINT)
+ # allow SIGINT exceptions to close the plot window.
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ try:
+ qApp.exec_()
+ finally:
+ # reset the SIGINT exception handler
+ signal.signal(signal.SIGINT, old_signal)
diff --git lib/matplotlib/backends/backend_qt5agg.py lib/matplotlib/backends/backend_qt5agg.py
index 4dec3b9cb..d0b11d5c9 100644
--- lib/matplotlib/backends/backend_qt5agg.py
+++ lib/matplotlib/backends/backend_qt5agg.py
@@ -4,6 +4,8 @@ Render to qt from agg.
import ctypes
+from qimage2ndarray import array2qimage
+
from matplotlib.transforms import Bbox
from .. import cbook
@@ -52,14 +54,15 @@ class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT):
buf = cbook._unmultiplied_rgba8888_to_premultiplied_argb32(
memoryview(reg))
- # clear the widget canvas
- painter.eraseRect(rect)
-
- qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0],
- QtGui.QImage.Format_ARGB32_Premultiplied)
+ # create a QImage from the array buffer
+ qimage = array2qimage(buf)
if hasattr(qimage, 'setDevicePixelRatio'):
# Not available on Qt4 or some older Qt5.
qimage.setDevicePixelRatio(self._dpi_ratio)
+
+ # clear the widget canvas
+ painter.eraseRect(rect)
+
origin = QtCore.QPoint(left, top)
painter.drawImage(origin / self._dpi_ratio, qimage)
# Adjust the buf reference count to work around a memory
diff --git lib/matplotlib/backends/qt_compat.py lib/matplotlib/backends/qt_compat.py
index 6b52284fe..7d52ac114 100644
--- lib/matplotlib/backends/qt_compat.py
+++ lib/matplotlib/backends/qt_compat.py
@@ -21,12 +21,14 @@ from matplotlib import rcParams
QT_API_PYQT5 = "PyQt5"
QT_API_PYSIDE2 = "PySide2"
QT_API_PYQTv2 = "PyQt4v2"
+QT_API_PYTHONQT = 'PythonQt' # use PythonQt API for Qt5
QT_API_PYSIDE = "PySide"
QT_API_PYQT = "PyQt4" # Use the old sip v1 API (Py3 defaults to v2).
QT_API_ENV = os.environ.get("QT_API")
# Mapping of QT_API_ENV to requested binding. ETS does not support PyQt4v1.
# (https://github.com/enthought/pyface/blob/master/pyface/qt/__init__.py)
_ETS = {"pyqt5": QT_API_PYQT5, "pyside2": QT_API_PYSIDE2,
+ "pythonqt": QT_API_PYTHONQT,
"pyqt": QT_API_PYQTv2, "pyside": QT_API_PYSIDE,
None: None}
# First, check if anything is already imported.
@@ -34,6 +36,8 @@ if "PyQt5.QtCore" in sys.modules:
QT_API = QT_API_PYQT5
elif "PySide2.QtCore" in sys.modules:
QT_API = QT_API_PYSIDE2
+elif "PythonQt.QtCore" in sys.modules:
+ QT_API = QT_API_PYTHONQT
elif "PyQt4.QtCore" in sys.modules:
QT_API = QT_API_PYQTv2
elif "PySide.QtCore" in sys.modules:
@@ -59,11 +63,11 @@ else:
except KeyError:
raise RuntimeError(
"The environment variable QT_API has the unrecognized value {!r};"
- "valid values are 'pyqt5', 'pyside2', 'pyqt', and 'pyside'")
+ "valid values are 'pyqt5', 'pyside2', 'pythonqt', pyqt', and 'pyside'")
def _setup_pyqt5():
- global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, _getSaveFileName
+ global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, is_qt5, _getSaveFileName
if QT_API == QT_API_PYQT5:
from PyQt5 import QtCore, QtGui, QtWidgets
@@ -71,18 +75,46 @@ def _setup_pyqt5():
QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
QtCore.Property = QtCore.pyqtProperty
+ _getSaveFileName = QtWidgets.QFileDialog.getSaveFileName
elif QT_API == QT_API_PYSIDE2:
from PySide2 import QtCore, QtGui, QtWidgets, __version__
+ _getSaveFileName = QtWidgets.QFileDialog.getSaveFileName
+ elif QT_API == QT_API_PYTHONQT: # try importing PythonQt
+ from PythonQt import QtCore, QtGui
+ __version__ = "3.2"
+ __version_info__ = "-"
+
+ # PythonQt does not yet support a getSaveFileName variant returning the selected filter
+ def _getSaveFileName(*args, **kwargs):
+ return (QtGui.QFileDialog.getSaveFileName(*args, **kwargs), None)
+
+ # Provide color getters
+ def getHslF(c):
+ return (c.hslHueF(), c.hslSaturationF(), c.lightnessF(), c.alphaF())
+
+ def getHsvF(c):
+ return (c.hueF(), c.saturationF(), c.valueF(), c.alphaF())
+
+ def getRgbF(c):
+ return (c.redF(), c.greenF(), c.blueF(), c.alphaF())
+
+ QtGui.QColor.getHslF = getHslF
+ QtGui.QColor.getHsvF = getHsvF
+ QtGui.QColor.getRgbF = getRgbF
+
+ # PythonQt doesn't have a separate QtWidgets module
+ QtWidgets = QtGui
else:
raise ValueError("Unexpected value for the 'backend.qt5' rcparam")
- _getSaveFileName = QtWidgets.QFileDialog.getSaveFileName
def is_pyqt5():
- return True
+ return QT_API in [QT_API_PYQT5, QT_API_PYSIDE2]
+ def is_qt5():
+ return True
def _setup_pyqt4():
- global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, _getSaveFileName
+ global QtCore, QtGui, QtWidgets, __version__, is_pyqt5, is_qt5, _getSaveFileName
def _setup_pyqt4_internal(api):
global QtCore, QtGui, QtWidgets, \
@@ -130,8 +162,11 @@ def _setup_pyqt4():
def is_pyqt5():
return False
+ def is_qt5():
+ return False
+
-if QT_API in [QT_API_PYQT5, QT_API_PYSIDE2]:
+if QT_API in [QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYTHONQT]:
_setup_pyqt5()
elif QT_API in [QT_API_PYQTv2, QT_API_PYSIDE, QT_API_PYQT]:
_setup_pyqt4()
diff --git lib/matplotlib/backends/qt_editor/formsubplottool.py lib/matplotlib/backends/qt_editor/formsubplottool.py
index a0914cab8..50a5ebed1 100644
--- lib/matplotlib/backends/qt_editor/formsubplottool.py
+++ lib/matplotlib/backends/qt_editor/formsubplottool.py
@@ -54,3 +54,9 @@ class UiSubplotTool(QtWidgets.QDialog):
right.addWidget(widget)
self._widgets["Close"].setFocus()
+
+ def accept(self):
+ QtWidgets.QDialog.accept(self)
+
+ def close(self):
+ QtWidgets.QDialog.close(self)
diff --git lib/matplotlib/pyplot.py lib/matplotlib/pyplot.py
index ce66bd894..4e77e1ec2 100644
--- lib/matplotlib/pyplot.py
+++ lib/matplotlib/pyplot.py
@@ -123,6 +123,10 @@ def install_repl_displayhook():
ipython_gui_name = backend2gui.get(get_backend())
if ipython_gui_name:
+ if ipython_gui_name.startswith('qt'):
+ from matplotlib.backends.qt_compat import QT_API
+ if QT_API == 'PythonQt':
+ ipython_gui_name = 'pythonqt'
ip.enable_gui(ipython_gui_name)
else:
_INSTALL_FIG_OBSERVER = True