Skip to content

Commit

Permalink
Implement backend for PyQt5 + modify Qt4 backend to Qt5 structure.
Browse files Browse the repository at this point in the history
A backend for PyQt5 based on intial work by @badders, modified to fix Qt5 mouse event handling,
then re-structured to implement as a wrapper over the existing Qt4 code. Following discussions on
this pull request:

matplotlib#2471

The code has been restructured to implement PyQt5 backend as a first-class implementation, with
other Qt backends (PyQt4, PyQt4v2, PySide (Qt v4)) wrapping it and modifying as required. The issues
of objects being moved around in the Qt namespace (many QtGui objects now in QtWidgets) QtWidgets is
simply assigned as a copy of QtGui if not available. This achieves the intended outcome with the
minimum code.

PySide required re-ordering of import on FigureCanvasQTAgg or paintEvent
function would not being called on FigureCanvasQTAggBase resulting in
black window.

A number of indentation, import and other fixes.
  • Loading branch information
Martin Fitzpatrick committed May 19, 2014
1 parent 24bc071 commit 4347da0
Show file tree
Hide file tree
Showing 16 changed files with 1,405 additions and 991 deletions.
777 changes: 13 additions & 764 deletions lib/matplotlib/backends/backend_qt4.py

Large diffs are not rendered by default.

114 changes: 5 additions & 109 deletions lib/matplotlib/backends/backend_qt4agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
import matplotlib
from matplotlib.figure import Figure

from .backend_qt5agg import new_figure_manager, NavigationToolbar2QTAgg
from .backend_qt5agg import FigureCanvasQTAggBase

from .backend_agg import FigureCanvasAgg
from .backend_qt4 import QtCore
from .backend_qt4 import QtGui
from .backend_qt4 import FigureManagerQT
from .backend_qt4 import FigureCanvasQT
from .backend_qt4 import NavigationToolbar2QT
Expand All @@ -39,21 +41,19 @@ def new_figure_manager(num, *args, **kwargs):
Create a new figure manager instance
"""
if DEBUG:
print('backend_qtagg.new_figure_manager')
print('backend_qt4agg.new_figure_manager')
FigureClass = kwargs.pop('FigureClass', Figure)
thisFig = FigureClass(*args, **kwargs)
return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
"""
Create a new figure manager instance for the given figure.
"""
canvas = FigureCanvasQTAgg(figure)
return FigureManagerQT(canvas, num)


class FigureCanvasQTAgg(FigureCanvasQT, FigureCanvasAgg):
class FigureCanvasQTAgg(FigureCanvasQTAggBase, FigureCanvasQT, FigureCanvasAgg):
"""
The canvas the figure renders into. Calls the draw and print fig
methods, creates the renderers, etc...
Expand Down Expand Up @@ -88,110 +88,6 @@ def __init__(self, figure):
else:
self._priv_update = self.update

def drawRectangle(self, rect):
self._drawRect = rect
self.repaint()

def paintEvent(self, e):
"""
Copy the image from the Agg canvas to the qt.drawable.
In Qt, all drawing should be done inside of here when a widget is
shown onscreen.
"""

#FigureCanvasQT.paintEvent(self, e)
if DEBUG:
print('FigureCanvasQtAgg.paintEvent: ', self,
self.get_width_height())

if self.blitbox is None:
# matplotlib is in rgba byte order. QImage wants to put the bytes
# into argb format and is in a 4 byte unsigned int. Little endian
# system is LSB first and expects the bytes in reverse order
# (bgra).
if QtCore.QSysInfo.ByteOrder == QtCore.QSysInfo.LittleEndian:
stringBuffer = self.renderer._renderer.tostring_bgra()
else:
stringBuffer = self.renderer._renderer.tostring_argb()

refcnt = sys.getrefcount(stringBuffer)

# convert the Agg rendered image -> qImage
qImage = QtGui.QImage(stringBuffer, self.renderer.width,
self.renderer.height,
QtGui.QImage.Format_ARGB32)
# get the rectangle for the image
rect = qImage.rect()
p = QtGui.QPainter(self)
# reset the image area of the canvas to be the back-ground color
p.eraseRect(rect)
# draw the rendered image on to the canvas
p.drawPixmap(QtCore.QPoint(0, 0), QtGui.QPixmap.fromImage(qImage))

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

# This works around a bug in PySide 1.1.2 on Python 3.x,
# where the reference count of stringBuffer is incremented
# but never decremented by QImage.
# TODO: revert PR #1323 once the issue is fixed in PySide.
del qImage
if refcnt != sys.getrefcount(stringBuffer):
_decref(stringBuffer)
else:
bbox = self.blitbox
l, b, r, t = bbox.extents
w = int(r) - int(l)
h = int(t) - int(b)
t = int(b) + h
reg = self.copy_from_bbox(bbox)
stringBuffer = reg.to_string_argb()
qImage = QtGui.QImage(stringBuffer, w, h,
QtGui.QImage.Format_ARGB32)
pixmap = QtGui.QPixmap.fromImage(qImage)
p = QtGui.QPainter(self)
p.drawPixmap(QtCore.QPoint(l, self.renderer.height-t), pixmap)
p.end()
self.blitbox = None
self._drawRect = None

def draw(self):
"""
Draw the figure with Agg, and queue a request
for a Qt draw.
"""
# The Agg draw is done here; delaying it until the paintEvent
# causes problems with code that uses the result of the
# draw() to update plot elements.
FigureCanvasAgg.draw(self)
self._priv_update()

def blit(self, bbox=None):
"""
Blit the region in bbox
"""
self.blitbox = bbox
l, b, w, h = bbox.bounds
t = b + h
self.repaint(l, self.renderer.height-t, w, h)

def print_figure(self, *args, **kwargs):
FigureCanvasAgg.print_figure(self, *args, **kwargs)
self.draw()


class NavigationToolbar2QTAgg(NavigationToolbar2QT):
def __init__(*args, **kwargs):
warnings.warn('This class has been deprecated in 1.4 ' +
'as it has no additional functionality over ' +
'`NavigationToolbar2QT`. Please change your code to '
'use `NavigationToolbar2QT` instead',
mplDeprecation)
NavigationToolbar2QT.__init__(*args, **kwargs)


FigureCanvas = FigureCanvasQTAgg
Expand Down
Loading

0 comments on commit 4347da0

Please sign in to comment.