Skip to content

Commit 984fba1

Browse files
committed
qt{4,5}cairo backend: the minimal version.
1 parent 090e9f1 commit 984fba1

File tree

11 files changed

+181
-127
lines changed

11 files changed

+181
-127
lines changed

doc/api/backend_qt4cairo_api.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
:mod:`matplotlib.backends.backend_qt4cairo`
3+
===========================================
4+
5+
.. automodule:: matplotlib.backends.backend_qt4cairo
6+
:members:
7+
:undoc-members:
8+
:show-inheritance:

doc/api/backend_qt5cairo_api.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
:mod:`matplotlib.backends.backend_qt5cairo`
3+
===========================================
4+
5+
.. automodule:: matplotlib.backends.backend_qt5cairo
6+
:members:
7+
:undoc-members:
8+
:show-inheritance:

doc/api/index_backend_api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ backends
1919
backend_pgf_api.rst
2020
backend_ps_api.rst
2121
backend_qt4agg_api.rst
22+
backend_qt4cairo_api.rst
2223
backend_qt5agg_api.rst
24+
backend_qt5cairo_api.rst
2325
backend_svg_api.rst
2426
backend_tkagg_api.rst
2527
backend_webagg_api.rst
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Cairo rendering for Qt canvases
2+
-------------------------------
3+
4+
The new ``Qt4Cairo`` and ``Qt5Cairo`` backends allow Qt canvases to use Cairo
5+
rendering instead of Agg.

lib/matplotlib/backends/backend_agg.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ def draw(self):
429429
# if toolbar:
430430
# toolbar.set_cursor(cursors.WAIT)
431431
self.figure.draw(self.renderer)
432+
# A GUI class may be need to update a window using this draw, so
433+
# don't forget to call the superclass.
434+
super(FigureCanvasAgg, self).draw()
432435
finally:
433436
# if toolbar:
434437
# toolbar.set_cursor(toolbar._lastCursor)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .backend_qt5cairo import _BackendQT5Cairo
2+
3+
4+
@_BackendQT5Cairo.export
5+
class _BackendQT4Cairo(_BackendQT5Cairo):
6+
pass

lib/matplotlib/backends/backend_qt5.py

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import signal
99
import sys
1010
from six import unichr
11+
import traceback
1112

1213
import matplotlib
1314

@@ -226,19 +227,16 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
226227
# QtCore.Qt.XButton2: None,
227228
}
228229

229-
def _update_figure_dpi(self):
230-
dpi = self._dpi_ratio * self.figure._original_dpi
231-
self.figure._set_dpi(dpi, forward=False)
232-
233230
@_allow_super_init
234231
def __init__(self, figure):
235232
_create_qApp()
236233
super(FigureCanvasQT, self).__init__(figure=figure)
237234

238-
figure._original_dpi = figure.dpi
239235
self.figure = figure
236+
# We don't want to scale up the figure DPI more than once.
237+
# Note, we don't handle a signal for changing DPI yet.
238+
figure._original_dpi = figure.dpi
240239
self._update_figure_dpi()
241-
self.resize(*self.get_width_height())
242240
# In cases with mixed resolution displays, we need to be careful if the
243241
# dpi_ratio changes - in this case we need to resize the canvas
244242
# accordingly. We could watch for screenChanged events from Qt, but
@@ -248,13 +246,23 @@ def __init__(self, figure):
248246
# needed.
249247
self._dpi_ratio_prev = None
250248

249+
self._draw_pending = False
250+
self._is_drawing = False
251+
self._draw_rect_callback = lambda painter: None
252+
253+
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
251254
self.setMouseTracking(True)
255+
self.resize(*self.get_width_height())
252256
# Key auto-repeat enabled by default
253257
self._keyautorepeat = True
254258

255259
palette = QtGui.QPalette(QtCore.Qt.white)
256260
self.setPalette(palette)
257261

262+
def _update_figure_dpi(self):
263+
dpi = self._dpi_ratio * self.figure._original_dpi
264+
self.figure._set_dpi(dpi, forward=False)
265+
258266
@property
259267
def _dpi_ratio(self):
260268
# Not available on Qt4 or some older Qt5.
@@ -263,6 +271,26 @@ def _dpi_ratio(self):
263271
except AttributeError:
264272
return 1
265273

274+
def _update_dpi(self):
275+
# As described in __init__ above, we need to be careful in cases with
276+
# mixed resolution displays if dpi_ratio is changing between painting
277+
# events.
278+
# Return whether we triggered a resizeEvent (and thus a paintEvent)
279+
# from within this function.
280+
if self._dpi_ratio != self._dpi_ratio_prev:
281+
# We need to update the figure DPI.
282+
self._update_figure_dpi()
283+
self._dpi_ratio_prev = self._dpi_ratio
284+
# The easiest way to resize the canvas is to emit a resizeEvent
285+
# since we implement all the logic for resizing the canvas for
286+
# that event.
287+
event = QtGui.QResizeEvent(self.size(), self.size())
288+
self.resizeEvent(event)
289+
# resizeEvent triggers a paintEvent itself, so we exit this one
290+
# (after making sure that the event is immediately handled).
291+
return True
292+
return False
293+
266294
def get_width_height(self):
267295
w, h = FigureCanvasBase.get_width_height(self)
268296
return int(w / self._dpi_ratio), int(h / self._dpi_ratio)
@@ -453,6 +481,60 @@ def stop_event_loop(self, event=None):
453481
if hasattr(self, "_event_loop"):
454482
self._event_loop.quit()
455483

484+
def draw(self):
485+
"""Render the figure, and queue a request for a Qt draw.
486+
"""
487+
# The renderer draw is done here; delaying causes problems with code
488+
# that uses the result of the draw() to update plot elements.
489+
if self._is_drawing:
490+
return
491+
self._is_drawing = True
492+
try:
493+
super(FigureCanvasQT, self).draw()
494+
finally:
495+
self._is_drawing = False
496+
self.update()
497+
498+
def draw_idle(self):
499+
"""Queue redraw of the Agg buffer and request Qt paintEvent.
500+
"""
501+
# The Agg draw needs to be handled by the same thread matplotlib
502+
# modifies the scene graph from. Post Agg draw request to the
503+
# current event loop in order to ensure thread affinity and to
504+
# accumulate multiple draw requests from event handling.
505+
# TODO: queued signal connection might be safer than singleShot
506+
if not (self._draw_pending or self._is_drawing):
507+
self._draw_pending = True
508+
QtCore.QTimer.singleShot(0, self._draw_idle)
509+
510+
def _draw_idle(self):
511+
if self.height() < 0 or self.width() < 0:
512+
self._draw_pending = False
513+
if not self._draw_pending:
514+
return
515+
try:
516+
self.draw()
517+
except Exception:
518+
# Uncaught exceptions are fatal for PyQt5, so catch them instead.
519+
traceback.print_exc()
520+
finally:
521+
self._draw_pending = False
522+
523+
def drawRectangle(self, rect):
524+
# Draw the zoom rectangle to the QPainter. _draw_rect_callback needs
525+
# to be called at the end of paintEvent.
526+
if rect is not None:
527+
def _draw_rect_callback(painter):
528+
pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio,
529+
QtCore.Qt.DotLine)
530+
painter.setPen(pen)
531+
painter.drawRect(*(pt / self._dpi_ratio for pt in rect))
532+
else:
533+
def _draw_rect_callback(painter):
534+
return
535+
self._draw_rect_callback = _draw_rect_callback
536+
self.update()
537+
456538

457539
class MainWindow(QtWidgets.QMainWindow):
458540
closing = QtCore.Signal()

lib/matplotlib/backends/backend_qt5agg.py

Lines changed: 14 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import six
88

99
import ctypes
10-
import traceback
1110

1211
from matplotlib import cbook
1312
from matplotlib.transforms import Bbox
@@ -19,32 +18,11 @@
1918
from .qt_compat import QT_API
2019

2120

22-
class FigureCanvasQTAggBase(FigureCanvasAgg):
23-
"""
24-
The canvas the figure renders into. Calls the draw and print fig
25-
methods, creates the renderers, etc...
26-
27-
Attributes
28-
----------
29-
figure : `matplotlib.figure.Figure`
30-
A high-level Figure instance
31-
32-
"""
21+
class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT):
3322

3423
def __init__(self, figure):
35-
super(FigureCanvasQTAggBase, self).__init__(figure=figure)
36-
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
37-
self._agg_draw_pending = False
38-
self._agg_is_drawing = False
24+
super(FigureCanvasQTAgg, self).__init__(figure=figure)
3925
self._bbox_queue = []
40-
self._drawRect = None
41-
42-
def drawRectangle(self, rect):
43-
if rect is not None:
44-
self._drawRect = [pt / self._dpi_ratio for pt in rect]
45-
else:
46-
self._drawRect = None
47-
self.update()
4826

4927
@property
5028
@cbook.deprecated("2.1")
@@ -57,27 +35,10 @@ def paintEvent(self, e):
5735
In Qt, all drawing should be done inside of here when a widget is
5836
shown onscreen.
5937
"""
60-
# if there is a pending draw, run it now as we need the updated render
61-
# to paint the widget
62-
if self._agg_draw_pending:
63-
self.__draw_idle_agg()
64-
# As described in __init__ above, we need to be careful in cases with
65-
# mixed resolution displays if dpi_ratio is changing between painting
66-
# events.
67-
if self._dpi_ratio != self._dpi_ratio_prev:
68-
# We need to update the figure DPI
69-
self._update_figure_dpi()
70-
self._dpi_ratio_prev = self._dpi_ratio
71-
# The easiest way to resize the canvas is to emit a resizeEvent
72-
# since we implement all the logic for resizing the canvas for
73-
# that event.
74-
event = QtGui.QResizeEvent(self.size(), self.size())
75-
# We use self.resizeEvent here instead of QApplication.postEvent
76-
# since the latter doesn't guarantee that the event will be emitted
77-
# straight away, and this causes visual delays in the changes.
78-
self.resizeEvent(event)
79-
# resizeEvent triggers a paintEvent itself, so we exit this one.
38+
if self._update_dpi():
39+
# The dpi update triggered its own paintEvent.
8040
return
41+
self._draw_idle() # Only does something if a draw is pending.
8142

8243
# if the canvas does not have a renderer, then give up and wait for
8344
# FigureCanvasAgg.draw(self) to be called
@@ -100,72 +61,20 @@ def paintEvent(self, e):
10061
reg = self.copy_from_bbox(bbox)
10162
buf = reg.to_string_argb()
10263
qimage = QtGui.QImage(buf, w, h, QtGui.QImage.Format_ARGB32)
64+
# Adjust the buf reference count to work around a memory leak bug
65+
# in QImage under PySide on Python 3.
66+
if QT_API == 'PySide' and six.PY3:
67+
ctypes.c_long.from_address(id(buf)).value = 1
10368
if hasattr(qimage, 'setDevicePixelRatio'):
10469
# Not available on Qt4 or some older Qt5.
10570
qimage.setDevicePixelRatio(self._dpi_ratio)
10671
origin = QtCore.QPoint(l, self.renderer.height - t)
10772
painter.drawImage(origin / self._dpi_ratio, qimage)
108-
# Adjust the buf reference count to work around a memory
109-
# leak bug in QImage under PySide on Python 3.
110-
if QT_API == 'PySide' and six.PY3:
111-
ctypes.c_long.from_address(id(buf)).value = 1
11273

113-
# draw the zoom rectangle to the QPainter
114-
if self._drawRect is not None:
115-
pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio,
116-
QtCore.Qt.DotLine)
117-
painter.setPen(pen)
118-
x, y, w, h = self._drawRect
119-
painter.drawRect(x, y, w, h)
74+
self._draw_rect_callback(painter)
12075

12176
painter.end()
12277

123-
def draw(self):
124-
"""Draw the figure with Agg, and queue a request for a Qt draw.
125-
"""
126-
# The Agg draw is done here; delaying causes problems with code that
127-
# uses the result of the draw() to update plot elements.
128-
if self._agg_is_drawing:
129-
return
130-
131-
self._agg_is_drawing = True
132-
try:
133-
super(FigureCanvasQTAggBase, self).draw()
134-
finally:
135-
self._agg_is_drawing = False
136-
self.update()
137-
138-
def draw_idle(self):
139-
"""Queue redraw of the Agg buffer and request Qt paintEvent.
140-
"""
141-
# The Agg draw needs to be handled by the same thread matplotlib
142-
# modifies the scene graph from. Post Agg draw request to the
143-
# current event loop in order to ensure thread affinity and to
144-
# accumulate multiple draw requests from event handling.
145-
# TODO: queued signal connection might be safer than singleShot
146-
if not (self._agg_draw_pending or self._agg_is_drawing):
147-
self._agg_draw_pending = True
148-
QtCore.QTimer.singleShot(0, self.__draw_idle_agg)
149-
150-
def __draw_idle_agg(self, *args):
151-
# if nothing to do, bail
152-
if not self._agg_draw_pending:
153-
return
154-
# we have now tried this function at least once, do not run
155-
# again until re-armed. Doing this here rather than after
156-
# protects against recursive calls triggered through self.draw
157-
# The recursive call is via `repaintEvent`
158-
self._agg_draw_pending = False
159-
# if negative size, bail
160-
if self.height() < 0 or self.width() < 0:
161-
return
162-
try:
163-
# actually do the drawing
164-
self.draw()
165-
except Exception:
166-
# Uncaught exceptions are fatal for PyQt5, so catch them instead.
167-
traceback.print_exc()
168-
16978
def blit(self, bbox=None):
17079
"""Blit the region in bbox.
17180
"""
@@ -182,23 +91,13 @@ def blit(self, bbox=None):
18291
self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h)
18392

18493
def print_figure(self, *args, **kwargs):
185-
super(FigureCanvasQTAggBase, self).print_figure(*args, **kwargs)
94+
super(FigureCanvasQTAgg, self).print_figure(*args, **kwargs)
18695
self.draw()
18796

18897

189-
class FigureCanvasQTAgg(FigureCanvasQTAggBase, FigureCanvasQT):
190-
"""
191-
The canvas the figure renders into. Calls the draw and print fig
192-
methods, creates the renderers, etc.
193-
194-
Modified to import from Qt5 backend for new-style mouse events.
195-
196-
Attributes
197-
----------
198-
figure : `matplotlib.figure.Figure`
199-
A high-level Figure instance
200-
201-
"""
98+
@cbook.deprecated("2.2")
99+
class FigureCanvasQTAggBase(FigureCanvasQTAgg):
100+
pass
202101

203102

204103
@_BackendQT5.export

0 commit comments

Comments
 (0)
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