Skip to content
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

Qt5: Move agg draw to main thread and fix rubberband #4972

Merged
merged 3 commits into from Sep 1, 2015
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/matplotlib/backend_bases.py
Expand Up @@ -2763,6 +2763,10 @@ def draw_rubberband(self, event, x0, y0, x1, y1):
"""Draw a rectangle rubberband to indicate zoom limits"""
pass

def remove_rubberband(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do any other backends implement this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at this time. I found a class named RubberbandBase in backend_tools.py that looks like a template for rubberband functionality, including a remove_rubberband function. Gtk3 and TK seem to implement this class. However, this code path is never used. Seems to me that this solution has been abandoned at some time and now only draw_rubberband implemented by the canvas is used.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other way around, the toolbase work is functionality that is just starting to be built out by @fariza and @OceanWolf

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pwuertz It's not abandoned, It's just the beginning. For now only Gtk3 and Tk are implemented, we are waiting for MEP27 to land to finish the other backends

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, well then porting this solution to the toolbase framework should be straight forward since it implements the same draw/remove concept.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the codepath does get used see FigureManagerGTK3 and FigureManagerTkAgg which calls this codepath via backend_tools.default_tools. In the upcoming 1.5 release this will go out as an experimental proof of concept, then we will begin work on the final polishing, MEP27 does this a bit (see #4143), and other commits related to MEP22 (see #3652). So for 2.1 we should have this ready to go out fully.

Feel free to test, just set rcParam['toolbar'] = 'toolmanager', but as we say, only on GTK3 and TKAgg for now.

"""Remove the rubberband"""
pass

def forward(self, *args):
"""Move forward in the view lim stack"""
self._views.forward()
Expand Down Expand Up @@ -3033,6 +3037,8 @@ def release_zoom(self, event):
self.canvas.mpl_disconnect(zoom_id)
self._ids_zoom = []

self.remove_rubberband()

if not self._xypress:
return

Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/backends/backend_qt4agg.py
Expand Up @@ -69,6 +69,7 @@ def __init__(self, figure):
if DEBUG:
print('FigureCanvasQtAgg: ', figure)
FigureCanvasQT.__init__(self, figure)
FigureCanvasQTAggBase.__init__(self, figure)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and not using super here. This was a change between qt4 and qt5 that I am not 100% sure we are handling correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also a bit confused concerning that change from qt4 to qt5. They prefer cooperative multiple inheritance now, while I got used to explicitly/manually calling all init functions, which is required when using PyQt4 I guess.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok this looks right to me now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With MEP27 we begin to introduce super into the backends, as we work to standardise the backends. Currently we do it for all of the GUI elements, apart from the canvas. Wx has an odd call signature for the constructor which makes super in FigureCanvas impossible until I have that fixed on master. I do have it fixed in my Wx branch of MEP27 but naturally that will wait until we have the main MEP27 PR into master.

FigureCanvasAgg.__init__(self, figure)
self._drawRect = None
self.blitbox = None
Expand Down
21 changes: 3 additions & 18 deletions lib/matplotlib/backends/backend_qt5.py
Expand Up @@ -239,7 +239,6 @@ def __init__(self, figure):
super(FigureCanvasQT, self).__init__(figure=figure)
self.figure = figure
self.setMouseTracking(True)
self._idle = True
w, h = self.get_width_height()
self.resize(w, h)

Expand Down Expand Up @@ -415,23 +414,6 @@ def stop_event_loop(self):

stop_event_loop.__doc__ = FigureCanvasBase.stop_event_loop_default.__doc__

def draw_idle(self):
# This cannot be a call to 'update', we need a slightly longer
# delay, otherwise mouse releases from zooming, panning, or
# lassoing might not finish processing and will not redraw properly.
# We use the guard flag to prevent infinite calls to 'draw_idle' which
# happens with the 'stale' figure & axes callbacks.
d = self._idle
self._idle = False

def idle_draw(*args):
try:
self.draw()
finally:
self._idle = True
if d:
QtCore.QTimer.singleShot(0, idle_draw)


class MainWindow(QtWidgets.QMainWindow):
closing = QtCore.Signal()
Expand Down Expand Up @@ -699,6 +681,9 @@ def draw_rubberband(self, event, x0, y0, x1, y1):
rect = [int(val)for val in (min(x0, x1), min(y0, y1), w, h)]
self.canvas.drawRectangle(rect)

def remove_rubberband(self):
self.canvas.drawRectangle(None)

def configure_subplots(self):
image = os.path.join(matplotlib.rcParams['datapath'],
'images', 'matplotlib.png')
Expand Down
52 changes: 37 additions & 15 deletions lib/matplotlib/backends/backend_qt5agg.py
Expand Up @@ -58,23 +58,23 @@ class FigureCanvasQTAggBase(object):

Public attribute

figure - A Figure instance
"""
figure - A Figure instance
"""

def __init__(self, figure):
super(FigureCanvasQTAggBase, self).__init__(figure=figure)
self._agg_draw_pending = False

def drawRectangle(self, rect):
self._drawRect = rect
self.draw_idle()
self.update()

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.
"""
# If we have not rendered the Agg backend yet, do so now.
if not hasattr(self, 'renderer'):
FigureCanvasAgg.draw(self)

# FigureCanvasQT.paintEvent(self, e)
if DEBUG:
print('FigureCanvasQtAgg.paintEvent: ', self,
Expand Down Expand Up @@ -136,21 +136,44 @@ def paintEvent(self, e):
pixmap = QtGui.QPixmap.fromImage(qImage)
p = QtGui.QPainter(self)
p.drawPixmap(QtCore.QPoint(l, self.renderer.height-t), pixmap)

# 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()
self.blitbox = None
self._drawRect = None

def draw(self):
"""
Draw the figure with Agg, and queue a request
for a Qt draw.
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.
# The Agg draw is done here; delaying causes problems with code that
# uses the result of the draw() to update plot elements.
FigureCanvasAgg.draw(self)
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:
self._agg_draw_pending = True
QtCore.QTimer.singleShot(0, self.__draw_idle_agg)

def __draw_idle_agg(self, *args):
try:
FigureCanvasAgg.draw(self)
self.update()
finally:
self._agg_draw_pending = False

def blit(self, bbox=None):
"""
Blit the region in bbox
Expand Down Expand Up @@ -186,8 +209,7 @@ class FigureCanvasQTAgg(FigureCanvasQTAggBase,
def __init__(self, figure):
if DEBUG:
print('FigureCanvasQtAgg: ', figure)
FigureCanvasQT.__init__(self, figure)
FigureCanvasAgg.__init__(self, figure)
super(FigureCanvasQTAgg, self).__init__(figure=figure)
self._drawRect = None
self.blitbox = None
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
Expand Down