Skip to content

qt{4,5}cairo backend: the minimal version. #10210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions doc/api/api_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ out what caused the breakage and how to fix it by updating your code.
For new features that were added to Matplotlib, please see
:ref:`whats-new`.

API Changes in 2.2.0
====================

.. toctree::
:glob:
:maxdepth: 1

next_api_changes/*


API Changes in 2.1.2
====================

Expand Down
8 changes: 8 additions & 0 deletions doc/api/backend_qt4cairo_api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

:mod:`matplotlib.backends.backend_qt4cairo`
===========================================

.. automodule:: matplotlib.backends.backend_qt4cairo
:members:
:undoc-members:
:show-inheritance:
8 changes: 8 additions & 0 deletions doc/api/backend_qt5cairo_api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

:mod:`matplotlib.backends.backend_qt5cairo`
===========================================

.. automodule:: matplotlib.backends.backend_qt5cairo
:members:
:undoc-members:
:show-inheritance:
2 changes: 2 additions & 0 deletions doc/api/index_backend_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ backends
backend_pgf_api.rst
backend_ps_api.rst
backend_qt4agg_api.rst
backend_qt4cairo_api.rst
backend_qt5agg_api.rst
backend_qt5cairo_api.rst
backend_svg_api.rst
backend_tkagg_api.rst
backend_webagg_api.rst
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Removal of deprecated rcParams
``````````````````````````````

The following deprecated rcParams have been removed:

- ``axes.color_cycle`` (see ``axes.prop_cycle``),
- ``legend.isaxes``,
- ``svg.embed_char_paths`` (see ``svg.fonttype``),
Expand Down
73 changes: 73 additions & 0 deletions doc/api/next_api_changes/2018-02-03-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
Changes to Qt backend class MRO
```````````````````````````````

To support both Agg and cairo rendering for Qt backends all of the
non-Agg specific code previously in
:class:`.backend_qt5agg.FigureCanvasQTAggBase` has been moved to
:class:`.backend_qt5.FigureCanvasQT` so it can be shared with the cairo
implementation. The :meth:`.FigureCanvasQTAggBase.paintEvent`,
:meth:`.FigureCanvasQTAggBase.blit`, and
:meth:`.FigureCanvasQTAggBase.print_figure` methods have moved to
:meth:`.FigureCanvasQTAgg.paintEvent`, :meth:`.FigureCanvasQTAgg.blit`, and
:meth:`.FigureCanvasQTAgg.print_figure`. The first two methods assume that
the instance is also a :class:`QWidget` so to use
:class:`FigureCanvasQTAggBase` it was required to multiple inherit
from a :class:`QWidget` sub-class.

Having moved all of its methods either up or down the class hierarchy
:class:`FigureCanvasQTAggBase` has been deprecated. To do this with
out warning and to preserve as much API as possible,
:class:`.backend_qt5.FigureCanvasQTAggBase` now inherits from
:class:`.backend_qt5.FigureCanvasQTAgg`.

The MRO for :class:`FigureCanvasQTAgg` and
:class:`FigureCanvasQTAggBase` used to be ::


[matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg,
matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase,
matplotlib.backends.backend_agg.FigureCanvasAgg,
matplotlib.backends.backend_qt5.FigureCanvasQT,
PyQt5.QtWidgets.QWidget,
PyQt5.QtCore.QObject,
sip.wrapper,
PyQt5.QtGui.QPaintDevice,
sip.simplewrapper,
matplotlib.backend_bases.FigureCanvasBase,
object]

and ::


[matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase,
matplotlib.backends.backend_agg.FigureCanvasAgg,
matplotlib.backend_bases.FigureCanvasBase,
object]


respectively. They are now ::

[matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg,
matplotlib.backends.backend_agg.FigureCanvasAgg,
matplotlib.backends.backend_qt5.FigureCanvasQT,
PyQt5.QtWidgets.QWidget,
PyQt5.QtCore.QObject,
sip.wrapper,
PyQt5.QtGui.QPaintDevice,
sip.simplewrapper,
matplotlib.backend_bases.FigureCanvasBase,
object]

and ::

[matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase,
matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg,
matplotlib.backends.backend_agg.FigureCanvasAgg,
matplotlib.backends.backend_qt5.FigureCanvasQT,
PyQt5.QtWidgets.QWidget,
PyQt5.QtCore.QObject,
sip.wrapper,
PyQt5.QtGui.QPaintDevice,
sip.simplewrapper,
matplotlib.backend_bases.FigureCanvasBase,
object]
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Adding API change notes
```````````````````````


For changes which require an entry in `api_changes.rst` please create
a file in this folder with the name :file:`YYYY-MM-DD-[initials].rst`
(ex :file:`2014-07-31-TAC.rst`) with contents following the form: ::
Expand Down
5 changes: 5 additions & 0 deletions doc/users/next_whats_new/more-cairo.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Cairo rendering for Qt canvases
-------------------------------

The new ``Qt4Cairo`` and ``Qt5Cairo`` backends allow Qt canvases to use Cairo
rendering instead of Agg.
3 changes: 3 additions & 0 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,9 @@ def draw(self):
# if toolbar:
# toolbar.set_cursor(cursors.WAIT)
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)
Expand Down
6 changes: 6 additions & 0 deletions lib/matplotlib/backends/backend_qt4cairo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .backend_qt5cairo import _BackendQT5Cairo


@_BackendQT5Cairo.export
class _BackendQT4Cairo(_BackendQT5Cairo):
pass
94 changes: 88 additions & 6 deletions lib/matplotlib/backends/backend_qt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import signal
import sys
from six import unichr
import traceback

import matplotlib

Expand Down Expand Up @@ -226,19 +227,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 +246,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 All @@ -263,6 +271,26 @@ def _dpi_ratio(self):
except AttributeError:
return 1

def _update_dpi(self):
# As described in __init__ above, we need to be careful in cases with
# mixed resolution displays if dpi_ratio is changing between painting
# events.
# Return whether we triggered a resizeEvent (and thus a paintEvent)
# from within this function.
if self._dpi_ratio != self._dpi_ratio_prev:
# We need to update the figure DPI.
self._update_figure_dpi()
self._dpi_ratio_prev = self._dpi_ratio
# The easiest way to resize the canvas is to emit a resizeEvent
# since we implement all the logic for resizing the canvas for
# that event.
event = QtGui.QResizeEvent(self.size(), self.size())
self.resizeEvent(event)
# resizeEvent triggers a paintEvent itself, so we exit this one
# (after making sure that the event is immediately handled).
return True
return False

def get_width_height(self):
w, h = FigureCanvasBase.get_width_height(self)
return int(w / self._dpi_ratio), int(h / self._dpi_ratio)
Expand Down Expand Up @@ -453,6 +481,60 @@ 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 renderer 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
if not self._draw_pending:
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
Loading