From 121acdcf960ddc4f1c3b81db71f2d50f06bbd3b6 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 01/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 6904799d46fd..e9032ad4050b 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 560f60ad46c51a38f6ab33c83938a37f6ab919b1 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 02/10] PEP8 removed trailing whitespace --- lib/matplotlib/backends/backend_qt4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index e9032ad4050b..6396996fa1c0 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -391,7 +391,7 @@ def __init__( self, canvas, num ): 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 )) From a564b9568de24ab08cdc709874daf59096f61b89 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell <“tcaswell@gmail.com”> Date: Fri, 18 Jan 2013 12:29:26 -0600 Subject: [PATCH 03/10] more white space corrections --- lib/matplotlib/backends/backend_qt4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index 6396996fa1c0..8923c5801640 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -389,7 +389,7 @@ 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) From b87bffd564943474be998592a358e048bc0273c7 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 e56eec2d389f7f614c78f647d4eb67241ca58668 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 8923c5801640..97a7cc352c15 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 7a929cab359d2a7da513fc8a166b959ac4030c2d 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 c613e71a20b857c79b0bc17e1edca15bbea33e35 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 97a7cc352c15..e31bb38ab073 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 582c72d54c574a0a1315fe93d8cf393c4a78850b 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 def3327ee7d4da22a39b598b8c6b460b855d587e 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 d636acb2513ed7d561ebe3fb98109c9e560ecc1d 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 c7e31415d19a..9ea1878d0a5a 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -1128,6 +1128,7 @@ def tk_window_focus(): 'matplotlib.tests.test_triangulation', 'matplotlib.tests.test_transforms', 'matplotlib.tests.test_arrow_patches', + 'matplotlib.tests.test_backend_qt4', ]