From 726e67ecdc91d57093c079f0ddce6c59872d1021 Mon Sep 17 00:00:00 2001 From: Damon McDougall Date: Thu, 17 Jan 2013 17:03:34 -0800 Subject: [PATCH 01/10] Merge pull request #1678 from tacaswell/qt4_closeevent added QtGui.QMainWindow.closeEvent() to make sure the close event --- lib/matplotlib/backends/backend_qt4.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 305db61c202f..e2e4419e9ad1 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -370,6 +370,7 @@ def idle_draw(*args): class MainWindow(QtGui.QMainWindow): def closeEvent(self, event): self.emit(QtCore.SIGNAL('closing()')) + QtGui.QMainWindow.closeEvent(self, event) class FigureManagerQT( FigureManagerBase ): """ From 45f8701a861238f7281507c18da3fc15e9a96db8 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell <“tcaswell@gmail.com”> Date: Fri, 18 Jan 2013 10:43:44 -0600 Subject: [PATCH 02/10] Re-wired signal/slot connections so that the figure in removed from Gcf when it is closed. In PR #1498 the attribute WA_DeleteOnClose was no longer set on the QtMainWindow object in QtFigureManager. The signal connection that was being used to remove the figure from Gcf when the window was closed was tied to the `destroyed()` signal of QtMainWindow, which is no longer being destroyed. Thus, gca and gcf would return references to no-longer visible figures/axes. _widgetclosed is now called when MainWindow emits 'closing()'. --- lib/matplotlib/backends/backend_qt4.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index e2e4419e9ad1..dc1771481bba 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -389,7 +389,9 @@ def __init__( self, canvas, num ): self.window = MainWindow() self.window.connect(self.window, QtCore.SIGNAL('closing()'), canvas.close_event) - + self.window.connect( self.window, QtCore.SIGNAL( 'closing()' ), + self._widgetclosed ) + self.window.setWindowTitle("Figure %d" % num) image = os.path.join( matplotlib.rcParams['datapath'],'images','matplotlib.png' ) self.window.setWindowIcon(QtGui.QIcon( image )) @@ -403,8 +405,7 @@ def __init__( self, canvas, num ): self.canvas.setFocusPolicy( QtCore.Qt.StrongFocus ) self.canvas.setFocus() - QtCore.QObject.connect( self.window, QtCore.SIGNAL( 'destroyed()' ), - self._widgetclosed ) + self.window._destroying = False self.toolbar = self._get_toolbar(self.canvas, self.window) From 35204ed9a1a305b2c8b7079acda6810bf14bfcf7 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell <“tcaswell@gmail.com”> Date: Fri, 18 Jan 2013 11:12:21 -0600 Subject: [PATCH 03/10] PEP8: whitespaces --- lib/matplotlib/backends/backend_qt4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index dc1771481bba..6eb441e7a182 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -389,9 +389,9 @@ def __init__( self, canvas, num ): self.window = MainWindow() self.window.connect(self.window, QtCore.SIGNAL('closing()'), canvas.close_event) - self.window.connect( self.window, QtCore.SIGNAL( 'closing()' ), + self.window.connect( self.window, QtCore.SIGNAL( 'closing()'), self._widgetclosed ) - + self.window.setWindowTitle("Figure %d" % num) image = os.path.join( matplotlib.rcParams['datapath'],'images','matplotlib.png' ) self.window.setWindowIcon(QtGui.QIcon( image )) From c3cb88214abc6b0ce0ddff0386472116ab417787 Mon Sep 17 00:00:00 2001 From: Damon McDougall Date: Fri, 18 Jan 2013 12:51:37 -0600 Subject: [PATCH 04/10] Add figure close test for qt4 backend --- lib/matplotlib/tests/test_backend_qt4.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 lib/matplotlib/tests/test_backend_qt4.py diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py new file mode 100644 index 000000000000..81175d51b16a --- /dev/null +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -0,0 +1,10 @@ +from matplotlib import rcParams +from matplotlib import pyplot as plt +from matplotlib.testing.decorators import cleanup + +@cleanup +def test_fig_close(): + rcParams['backend'] = 'qt4agg' + + fig = plt.figure() + fig.close() From 3d3f8c3cccf6d5c2e6479b8ac883c0b0d0a01c64 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 19 Jan 2013 15:01:28 -0600 Subject: [PATCH 05/10] PEP8 fixes to FigureManagerQT class --- lib/matplotlib/backends/backend_qt4.py | 55 +++++++++++++++----------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 6eb441e7a182..5e94b5472c7b 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -367,12 +367,14 @@ def idle_draw(*args): self._idle = True if d: QtCore.QTimer.singleShot(0, idle_draw) + class MainWindow(QtGui.QMainWindow): def closeEvent(self, event): self.emit(QtCore.SIGNAL('closing()')) QtGui.QMainWindow.closeEvent(self, event) -class FigureManagerQT( FigureManagerBase ): + +class FigureManagerQT(FigureManagerBase): """ Public attributes @@ -382,19 +384,20 @@ class FigureManagerQT( FigureManagerBase ): window : The qt.QMainWindow """ - def __init__( self, canvas, num ): - if DEBUG: print('FigureManagerQT.%s' % fn_name()) - FigureManagerBase.__init__( self, canvas, num ) + def __init__(self, canvas, num): + if DEBUG: + print('FigureManagerQT.%s' % fn_name()) + FigureManagerBase.__init__(self, canvas, num) self.canvas = canvas self.window = MainWindow() self.window.connect(self.window, QtCore.SIGNAL('closing()'), - canvas.close_event) - self.window.connect( self.window, QtCore.SIGNAL( 'closing()'), - self._widgetclosed ) + canvas.close_event) + self.window.connect(self.window, QtCore.SIGNAL('closing()'), + self._widgetclosed) self.window.setWindowTitle("Figure %d" % num) - image = os.path.join( matplotlib.rcParams['datapath'],'images','matplotlib.png' ) - self.window.setWindowIcon(QtGui.QIcon( image )) + image = os.path.join(matplotlib.rcParams['datapath'], 'images', 'matplotlib.png') + self.window.setWindowIcon(QtGui.QIcon(image)) # Give the keyboard focus to the figure instead of the # manager; StrongFocus accepts both tab and click to focus and @@ -402,10 +405,9 @@ def __init__( self, canvas, num ): # ClickFocus only takes the focus is the window has been # clicked # on. http://developer.qt.nokia.com/doc/qt-4.8/qt.html#FocusPolicy-enum - self.canvas.setFocusPolicy( QtCore.Qt.StrongFocus ) + self.canvas.setFocusPolicy(QtCore.Qt.StrongFocus) self.canvas.setFocus() - self.window._destroying = False self.toolbar = self._get_toolbar(self.canvas, self.window) @@ -421,7 +423,7 @@ def __init__( self, canvas, num ): # requested size: cs = canvas.sizeHint() sbs = self.window.statusBar().sizeHint() - self._status_and_tool_height = tbs_height+sbs.height() + self._status_and_tool_height = tbs_height + sbs.height() height = cs.height() + self._status_and_tool_height self.window.resize(cs.width(), height) @@ -430,14 +432,14 @@ def __init__( self, canvas, num ): if matplotlib.is_interactive(): self.window.show() - def notify_axes_change( fig ): + def notify_axes_change(fig): # This will be called whenever the current axes is changed if self.toolbar is not None: self.toolbar.update() - self.canvas.figure.add_axobserver( notify_axes_change ) + self.canvas.figure.add_axobserver(notify_axes_change) @QtCore.Slot() - def _show_message(self,s): + def _show_message(self, s): # Fixes a PySide segfault. self.window.statusBar().showMessage(s) @@ -447,8 +449,9 @@ def full_screen_toggle(self): else: self.window.showFullScreen() - def _widgetclosed( self ): - if self.window._destroying: return + def _widgetclosed(self): + if self.window._destroying: + return self.window._destroying = True try: Gcf.destroy(self.num) @@ -476,15 +479,19 @@ def resize(self, width, height): def show(self): self.window.show() - def destroy( self, *args ): + def destroy(self, *args): # check for qApp first, as PySide deletes it in its atexit handler - if QtGui.QApplication.instance() is None: return - if self.window._destroying: return + if QtGui.QApplication.instance() is None: + return + if self.window._destroying: + return self.window._destroying = True - QtCore.QObject.disconnect( self.window, QtCore.SIGNAL( 'destroyed()' ), - self._widgetclosed ) - if self.toolbar: self.toolbar.destroy() - if DEBUG: print("destroy figure manager") + QtCore.QObject.disconnect(self.window, QtCore.SIGNAL('destroyed()'), + self._widgetclosed) + if self.toolbar: + self.toolbar.destroy() + if DEBUG: + print("destroy figure manager") self.window.close() def get_window_title(self): From 75a92df91a059f87fe3be956adef44765346c9e2 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 19 Jan 2013 15:04:21 -0600 Subject: [PATCH 06/10] test for issue #1683 --- lib/matplotlib/tests/test_backend_qt4.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index 81175d51b16a..9c551acb0f92 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -1,10 +1,25 @@ from matplotlib import rcParams from matplotlib import pyplot as plt from matplotlib.testing.decorators import cleanup +from matplotlib._pylab_helpers import Gcf +import copy + @cleanup def test_fig_close(): - rcParams['backend'] = 'qt4agg' + # force switch to the Qt4 backend + plt.switch_backend('Qt4Agg') + + #save the state of Gcf.figs + init_figs = copy.copy(Gcf.figs) + # make a figure using pyplot interface fig = plt.figure() - fig.close() + + # simulate user clicking the close button by reaching in + # and calling close on the underlying Qt object + fig.canvas.manager.window.close() + + # assert that we have removed the reference to the FigureManager + # that got added by plt.figure() + assert(init_figs == Gcf.figs) From 24577459cba4277ba2e89c103074e130c687dcfd Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 19 Jan 2013 15:16:22 -0600 Subject: [PATCH 07/10] fixed link rot --- lib/matplotlib/backends/backend_qt4.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 5e94b5472c7b..933016f80e03 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -404,7 +404,8 @@ def __init__(self, canvas, num): # will enable the canvas to process event w/o clicking. # ClickFocus only takes the focus is the window has been # clicked - # on. http://developer.qt.nokia.com/doc/qt-4.8/qt.html#FocusPolicy-enum + # on. http://qt-project.org/doc/qt-4.8/qt.html#FocusPolicy-enum or + # http://doc.qt.digia.com/qt/qt.html#FocusPolicy-enum self.canvas.setFocusPolicy(QtCore.Qt.StrongFocus) self.canvas.setFocus() From 4ed4695989f4659dc4dc402684d7766c24f6ae2c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 19 Jan 2013 15:37:54 -0600 Subject: [PATCH 08/10] removed unused import --- lib/matplotlib/tests/test_backend_qt4.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index 9c551acb0f92..c638678c03d1 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -1,4 +1,3 @@ -from matplotlib import rcParams from matplotlib import pyplot as plt from matplotlib.testing.decorators import cleanup from matplotlib._pylab_helpers import Gcf From 7362cdc35d0558f80efedd55b481a1eaddcb02b2 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 23 Jan 2013 15:21:45 -0600 Subject: [PATCH 09/10] added knownfailureif to test based on if qt4_compat could be imported, as this should hit either PySide or PyQt4, depending on which is installed. (I have faith that the setup code that will make sure that if only one of them is installed, it defaults to using that one) --- lib/matplotlib/tests/test_backend_qt4.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/matplotlib/tests/test_backend_qt4.py b/lib/matplotlib/tests/test_backend_qt4.py index c638678c03d1..7806030e9727 100644 --- a/lib/matplotlib/tests/test_backend_qt4.py +++ b/lib/matplotlib/tests/test_backend_qt4.py @@ -1,10 +1,18 @@ from matplotlib import pyplot as plt from matplotlib.testing.decorators import cleanup +from matplotlib.testing.decorators import knownfailureif from matplotlib._pylab_helpers import Gcf import copy +try: + import matplotlib.backends.qt4_compat + HAS_QT = True +except ImportError: + HAS_QT = False + @cleanup +@knownfailureif(not HAS_QT) def test_fig_close(): # force switch to the Qt4 backend plt.switch_backend('Qt4Agg') From 671e0ec52cfce9675b604a4d6d5ff3f77c2d087d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell <“tcaswell@gmail.com”> Date: Thu, 24 Jan 2013 23:51:04 -0600 Subject: [PATCH 10/10] added test_backend_qt4 to test list --- lib/matplotlib/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 404eb09248fd..2dde8c3a3a8d 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1122,6 +1122,7 @@ def tk_window_focus(): 'matplotlib.tests.test_triangulation', 'matplotlib.tests.test_transforms', 'matplotlib.tests.test_arrow_patches', + 'matplotlib.tests.test_backend_qt4', ]