Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Qt5Cairo backend.
  • Loading branch information
anntzer committed Jan 9, 2018
commit 2abdcea1ea2b17c3780de8490fd7e078472d08c3
10 changes: 5 additions & 5 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,18 +422,18 @@ def draw(self):
Draw the figure using the renderer
"""
self.renderer = self.get_renderer(cleared=True)
# acquire a lock on the shared font cache
RendererAgg.lock.acquire()

toolbar = self.toolbar
try:
if toolbar:
toolbar.set_cursor(cursors.WAIT)
self.figure.draw(self.renderer)
with RendererAgg.lock:
self.figure.draw(self.renderer)
# A GUI class may be need to update a window using this draw, so
# don't forget to call the superclass.
super(FigureCanvasAgg, self).draw()
finally:
if toolbar:
toolbar.set_cursor(toolbar._lastCursor)
RendererAgg.lock.release()

def get_renderer(self, cleared=False):
l, b, w, h = self.figure.bbox.bounds
Expand Down
76 changes: 68 additions & 8 deletions lib/matplotlib/backends/backend_qt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import re
import signal
import sys
from six import unichr
import traceback

import matplotlib

Expand Down Expand Up @@ -226,19 +226,16 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
# QtCore.Qt.XButton2: None,
}

def _update_figure_dpi(self):
dpi = self._dpi_ratio * self.figure._original_dpi
self.figure._set_dpi(dpi, forward=False)

@_allow_super_init
def __init__(self, figure):
_create_qApp()
super(FigureCanvasQT, self).__init__(figure=figure)

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

self._draw_pending = False
self._is_drawing = False
self._draw_rect_callback = lambda painter: None

self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
self.setMouseTracking(True)
self.resize(*self.get_width_height())
# Key auto-repeat enabled by default
self._keyautorepeat = True

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

def _update_figure_dpi(self):
dpi = self._dpi_ratio * self.figure._original_dpi
self.figure._set_dpi(dpi, forward=False)

@property
def _dpi_ratio(self):
# Not available on Qt4 or some older Qt5.
Expand Down Expand Up @@ -408,7 +415,7 @@ def _get_key(self, event):
if event_key > MAX_UNICODE:
return None

key = unichr(event_key)
key = six.unichr(event_key)
# qt delivers capitalized letters. fix capitalization
# note that capslock is ignored
if 'shift' in mods:
Expand Down Expand Up @@ -453,6 +460,59 @@ def stop_event_loop(self, event=None):
if hasattr(self, "_event_loop"):
self._event_loop.quit()

def draw(self):
"""Render the figure, and queue a request for a Qt draw.
"""
# The Agg draw is done here; delaying causes problems with code that
# uses the result of the draw() to update plot elements.
if self._is_drawing:
return
self._is_drawing = True
try:
super(FigureCanvasQT, self).draw()
finally:
self._is_drawing = False
self.update()

def draw_idle(self):
"""Queue redraw of the Agg buffer and request Qt paintEvent.
"""
# The Agg draw needs to be handled by the same thread matplotlib
# modifies the scene graph from. Post Agg draw request to the
# current event loop in order to ensure thread affinity and to
# accumulate multiple draw requests from event handling.
# TODO: queued signal connection might be safer than singleShot
if not (self._draw_pending or self._is_drawing):
self._draw_pending = True
QtCore.QTimer.singleShot(0, self._draw_idle)

def _draw_idle(self):
if self.height() < 0 or self.width() < 0:
self._draw_pending = False
return
try:
self.draw()
except Exception:
# Uncaught exceptions are fatal for PyQt5, so catch them instead.
traceback.print_exc()
finally:
self._draw_pending = False

def drawRectangle(self, rect):
# Draw the zoom rectangle to the QPainter. _draw_rect_callback needs
# to be called at the end of paintEvent.
if rect is not None:
def _draw_rect_callback(painter):
pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio,
QtCore.Qt.DotLine)
painter.setPen(pen)
painter.drawRect(*(pt / self._dpi_ratio for pt in rect))
else:
def _draw_rect_callback(painter):
return
self._draw_rect_callback = _draw_rect_callback
self.update()


class MainWindow(QtWidgets.QMainWindow):
closing = QtCore.Signal()
Expand Down
84 changes: 5 additions & 79 deletions lib/matplotlib/backends/backend_qt5agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import six

import ctypes
import traceback

from matplotlib import cbook
from matplotlib.transforms import Bbox
Expand All @@ -19,32 +18,11 @@
from .qt_compat import QT_API


class FigureCanvasQTAggBase(FigureCanvasAgg):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc...

Attributes
----------
figure : `matplotlib.figure.Figure`
A high-level Figure instance

"""
class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT):

def __init__(self, figure):
super(FigureCanvasQTAggBase, self).__init__(figure=figure)
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
self._agg_draw_pending = False
self._agg_is_drawing = False
super(FigureCanvasQTAgg, self).__init__(figure=figure)
self._bbox_queue = []
self._drawRect = None

def drawRectangle(self, rect):
if rect is not None:
self._drawRect = [pt / self._dpi_ratio for pt in rect]
else:
self._drawRect = None
self.update()

@property
@cbook.deprecated("2.1")
Expand Down Expand Up @@ -110,13 +88,7 @@ def paintEvent(self, e):
if QT_API == 'PySide' and six.PY3:
ctypes.c_long.from_address(id(buf)).value = 1

# draw the zoom rectangle to the QPainter
if self._drawRect is not None:
pen = QtGui.QPen(QtCore.Qt.black, 1 / self._dpi_ratio,
QtCore.Qt.DotLine)
painter.setPen(pen)
x, y, w, h = self._drawRect
painter.drawRect(x, y, w, h)
self._draw_rect_callback(painter)

painter.end()

Expand All @@ -130,42 +102,11 @@ def draw(self):

self._agg_is_drawing = True
try:
super(FigureCanvasQTAggBase, self).draw()
super(FigureCanvasQTAgg, self).draw()
finally:
self._agg_is_drawing = False
self.update()

def draw_idle(self):
"""Queue redraw of the Agg buffer and request Qt paintEvent.
"""
# The Agg draw needs to be handled by the same thread matplotlib
# modifies the scene graph from. Post Agg draw request to the
# current event loop in order to ensure thread affinity and to
# accumulate multiple draw requests from event handling.
# TODO: queued signal connection might be safer than singleShot
if not (self._agg_draw_pending or self._agg_is_drawing):
self._agg_draw_pending = True
QtCore.QTimer.singleShot(0, self.__draw_idle_agg)

def __draw_idle_agg(self, *args):
# if nothing to do, bail
if not self._agg_draw_pending:
return
# we have now tried this function at least once, do not run
# again until re-armed. Doing this here rather than after
# protects against recursive calls triggered through self.draw
# The recursive call is via `repaintEvent`
self._agg_draw_pending = False
# if negative size, bail
if self.height() < 0 or self.width() < 0:
return
try:
# actually do the drawing
self.draw()
except Exception:
# Uncaught exceptions are fatal for PyQt5, so catch them instead.
traceback.print_exc()

def blit(self, bbox=None):
"""Blit the region in bbox.
"""
Expand All @@ -182,25 +123,10 @@ def blit(self, bbox=None):
self.repaint(l, self.renderer.height / self._dpi_ratio - t, w, h)

def print_figure(self, *args, **kwargs):
super(FigureCanvasQTAggBase, self).print_figure(*args, **kwargs)
super(FigureCanvasQTAgg, self).print_figure(*args, **kwargs)
self.draw()


class FigureCanvasQTAgg(FigureCanvasQTAggBase, FigureCanvasQT):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc.

Modified to import from Qt5 backend for new-style mouse events.

Attributes
----------
figure : `matplotlib.figure.Figure`
A high-level Figure instance

"""


@_BackendQT5.export
class _BackendQT5Agg(_BackendQT5):
FigureCanvas = FigureCanvasQTAgg
29 changes: 29 additions & 0 deletions lib/matplotlib/backends/backend_qt5cairo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from . import backend_cairo # Keep the RendererCairo class swappable.
from .backend_qt5 import QtCore, QtGui, _BackendQT5, FigureCanvasQT


class FigureCanvasQTCairo(FigureCanvasQT):
def __init__(self, figure):
super(FigureCanvasQTCairo, self).__init__(figure=figure)
self._renderer = backend_cairo.RendererCairo(self.figure.dpi)

def paintEvent(self, event):
width = self.width()
height = self.height()
surface = backend_cairo.cairo.ImageSurface(
backend_cairo.cairo.FORMAT_ARGB32, width, height)
self._renderer.set_ctx_from_surface(surface)
# This should be really done by set_ctx_from_surface...
self._renderer.set_width_height(width, height)
self.figure.draw(self._renderer)
qimage = QtGui.QImage(surface.get_data(), width, height,
QtGui.QImage.Format_ARGB32_Premultiplied)
painter = QtGui.QPainter(self)
painter.drawImage(0, 0, qimage)
self._draw_rect_callback(painter)
painter.end()


@_BackendQT5.export
class _BackendQT5Cairo(_BackendQT5):
FigureCanvas = FigureCanvasQTCairo
2 changes: 1 addition & 1 deletion lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
# change for later versions.

interactive_bk = ['GTK', 'GTKAgg', 'GTKCairo', 'MacOSX',
'Qt4Agg', 'Qt5Agg', 'TkAgg', 'WX', 'WXAgg',
'Qt4Agg', 'Qt5Agg', 'Qt5Cairo', 'TkAgg', 'WX', 'WXAgg',
'GTK3Cairo', 'GTK3Agg', 'WebAgg', 'nbAgg']


Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/tests/test_backends_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def _get_testable_interactive_backends():
for deps, backend in [(["cairocffi", "pgi"], "gtk3agg"),
(["cairocffi", "pgi"], "gtk3cairo"),
(["PyQt5"], "qt5agg"),
(["cairocffi", "PyQt5"], "qt5cairo"),
(["tkinter"], "tkagg"),
(["wx"], "wxagg")]:
reason = None
Expand Down