From 9b53c97bccd748fa891c281bd968a03fa3e24e62 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Thu, 16 Jul 2015 14:21:37 +0100
Subject: [PATCH] DisplayContext now has a destroy method, which is called by
 the FSLViewFrame when the associated ViewPanel is closed. Lots of fixes still
 to be made.

---
 fsl/fslview/actions/__init__.py              | 16 +++++++++-------
 fsl/fslview/displaycontext/displaycontext.py |  7 +++++++
 fsl/fslview/frame.py                         |  8 +++++---
 fsl/fslview/panel.py                         | 18 ++++++++----------
 fsl/fslview/profiles/__init__.py             |  4 ++--
 fsl/fslview/profiles/lightboxviewprofile.py  |  3 ++-
 fsl/fslview/views/canvaspanel.py             |  6 +++---
 fsl/fslview/views/lightboxpanel.py           |  3 ++-
 fsl/fslview/views/viewpanel.py               |  9 +++++++--
 9 files changed, 45 insertions(+), 29 deletions(-)

diff --git a/fsl/fslview/actions/__init__.py b/fsl/fslview/actions/__init__.py
index 7a7b242a0..cedaa72f4 100644
--- a/fsl/fslview/actions/__init__.py
+++ b/fsl/fslview/actions/__init__.py
@@ -202,15 +202,17 @@ class ActionProvider(props.HasProperties):
             act = Action(overlayList, displayCtx, action=func)
             self.__actions[name] = act
 
-        log.memory('{}.init ({})'.format(type(self).__name__, id(self)))
-
-
-    def __del__(self):
-        """
+            
+    def destroy(self):
+        """This method should be called when this ``ActionProvider`` is
+        about to be destroyed. It ensures that all ``Action`` instances
+        are cleared.
         """
-        log.memory('{}.del ({})'.format(type(self).__name__, id(self)))
+        for _, act in self.__actions.items():
+            act.unbindAllWidgets()
+            
         self.__actions = None
-                  
+
             
     def addActionToggleListener(self, name, listenerName, func):
         """Add a listener function which will be called when the named action
diff --git a/fsl/fslview/displaycontext/displaycontext.py b/fsl/fslview/displaycontext/displaycontext.py
index 03e6aac7a..8ab23f27e 100644
--- a/fsl/fslview/displaycontext/displaycontext.py
+++ b/fsl/fslview/displaycontext/displaycontext.py
@@ -93,7 +93,14 @@ class DisplayContext(props.SyncableHasProperties):
         
     def __del__(self):
         log.memory('{}.del ({})'.format(type(self).__name__, id(self)))
+
         
+    def destroy(self):
+
+        for overlay, display in self.__displays.items():
+            display.destroy()
+
+        self.__displays = None
 
         
     def getDisplay(self, overlay, overlayType=None):
diff --git a/fsl/fslview/frame.py b/fsl/fslview/frame.py
index 3530734c1..4bd7990b2 100644
--- a/fsl/fslview/frame.py
+++ b/fsl/fslview/frame.py
@@ -98,6 +98,7 @@ class FSLViewFrame(wx.Frame):
         # Keeping track of all
         # open view panels
         self.__viewPanels      = []
+        self.__viewPanelDCs    = {}
         self.__viewPanelTitles = {}
         self.__viewPanelMenus  = {}
         self.__viewPanelCount  = 0
@@ -141,6 +142,7 @@ class FSLViewFrame(wx.Frame):
 
         self.__viewPanels.append(panel)
         self.__viewPanelTitles[panel] = title
+        self.__viewPanelDCs[   panel] = childDC
         
         self.__centrePane.AddPage(panel, title, True)
         self.__centrePane.Split(
@@ -184,6 +186,7 @@ class FSLViewFrame(wx.Frame):
         self.__viewPanels             .remove(panel)
         self.__viewPanelMenus         .pop(   panel, None)
         title = self.__viewPanelTitles.pop(   panel)
+        dctx  = self.__viewPanelDCs   .pop(   panel)
 
         log.debug('Destroying view panel {} ({})'.format(
             title, type(panel).__name__))
@@ -200,10 +203,9 @@ class FSLViewFrame(wx.Frame):
 
         # Calling fslpanel.FSLViewPanel.destroy()
         # - I think that the AUINotebook does the
-        # wx.Window.Destroy side of things, which
-        # will eventually result in panel.__del__
-        # ...
+        # wx.Window.Destroy side of things ...
         panel.destroy()
+        dctx .destroy()
 
         
     def __onClose(self, ev):
diff --git a/fsl/fslview/panel.py b/fsl/fslview/panel.py
index 4c57902d5..e88572f85 100644
--- a/fsl/fslview/panel.py
+++ b/fsl/fslview/panel.py
@@ -113,20 +113,18 @@ class _FSLViewPanel(actions.ActionProvider):
 
         Overriding subclass implementations must call this base class
         method, otherwise memory leaks will probably occur, and warnings will
-        probably be output to the log (see :meth:`__del__`).
+        probably be output to the log (see :meth:`__del__`). This
+        implememtation should be called after the subclass has performed its
+        own clean-up, as this method expliciltly clears the ``_overlayList``
+        and ``_displayCtx`` references.
         """
-        self.__destroyed = True
+        actions.ActionProvider.destroy(self)
+        self._displayCtx  = None
+        self._overlayList = None
+        self.__destroyed  = True
 
     
     def __del__(self):
-        """Sub-classes which implement ``__del__`` must call this
-        implementation, otherwise memory leaks will occur.
-        """
-        actions.ActionProvider.__del__(self)
-
-        self._overlayList = None
-        self._displayCtx  = None
-
         if not self.__destroyed:
             log.warning('The {}.destroy() method has not been called '
                         '- unless the application is shutting down, '
diff --git a/fsl/fslview/profiles/__init__.py b/fsl/fslview/profiles/__init__.py
index 1bcc5aecc..f074175b5 100644
--- a/fsl/fslview/profiles/__init__.py
+++ b/fsl/fslview/profiles/__init__.py
@@ -193,8 +193,6 @@ class Profile(actions.ActionProvider):
         is called by the :class:`ProfileManager` when  this ``Profile``
         instance is no longer needed.  
         """
-        self.deregister()
-        
         self._viewPanel   = None
         self._overlayList = None
         self._displayCtx  = None
@@ -591,6 +589,7 @@ class ProfileManager(object):
         important object references to avoid memory leaks.
         """
         if self._currentProfile is not None:
+            self._currentProfile.deregister()
             self._currentProfile.destroy()
             
         self._currentProfile    = None
@@ -622,6 +621,7 @@ class ProfileManager(object):
             log.debug('Deregistering {} profile from {}'.format(
                 self._currentProfile.__class__.__name__,
                 self._viewCls.__name__))
+            self._currentProfile.deregister()
             self._currentProfile.destroy()
                
         self._currentProfile = profileCls(self._viewPanel,
diff --git a/fsl/fslview/profiles/lightboxviewprofile.py b/fsl/fslview/profiles/lightboxviewprofile.py
index 1d8e3b298..338e75564 100644
--- a/fsl/fslview/profiles/lightboxviewprofile.py
+++ b/fsl/fslview/profiles/lightboxviewprofile.py
@@ -21,8 +21,9 @@ class LightBoxViewProfile(profiles.Profile):
                                   displayCtx,
                                   modes=['view', 'zoom'])
 
-        self._canvas = canvasPanel.getCanvas()
+        self._canvas = viewPanel.getCanvas()
 
+        
     def getEventTargets(self):
         return [self._canvas]
 
diff --git a/fsl/fslview/views/canvaspanel.py b/fsl/fslview/views/canvaspanel.py
index ecb724f49..3a559c243 100644
--- a/fsl/fslview/views/canvaspanel.py
+++ b/fsl/fslview/views/canvaspanel.py
@@ -245,11 +245,11 @@ class CanvasPanel(viewpanel.ViewPanel):
         cleanly.
         """
 
-        viewpanel.ViewPanel.destroy(self)
-
         if self.__colourBar is not None:
             self.__colourBar.destroy()
-
+            
+        viewpanel.ViewPanel.destroy(self)
+            
     
     def screenshot(self, *a):
         _takeScreenShot(self._overlayList, self._displayCtx, self)
diff --git a/fsl/fslview/views/lightboxpanel.py b/fsl/fslview/views/lightboxpanel.py
index 46e10c741..54178ab81 100644
--- a/fsl/fslview/views/lightboxpanel.py
+++ b/fsl/fslview/views/lightboxpanel.py
@@ -133,12 +133,13 @@ class LightBoxPanel(canvaspanel.CanvasPanel):
 
     def destroy(self):
         """Removes property listeners"""
-        canvaspanel.CanvasPanel.destroy(self)
 
         self._displayCtx .removeListener('location',        self._name)
         self._displayCtx .removeListener('selectedOverlay', self._name)
         self._overlayList.removeListener('overlays',        self._name)
 
+        canvaspanel.CanvasPanel.destroy(self)
+
         
     def _selectedOverlayChanged(self, *a):
         """Called when the selected overlay changes.
diff --git a/fsl/fslview/views/viewpanel.py b/fsl/fslview/views/viewpanel.py
index a7da9444e..deb698491 100644
--- a/fsl/fslview/views/viewpanel.py
+++ b/fsl/fslview/views/viewpanel.py
@@ -138,8 +138,6 @@ class ViewPanel(fslpanel.FSLViewPanel):
     def destroy(self):
         """
         """
-
-        fslpanel.FSLViewPanel.destroy(self)
         
         # Make sure that any control panels are correctly destroyed
         for panelType, panel in self.__panels.items():
@@ -159,9 +157,16 @@ class ViewPanel(fslpanel.FSLViewPanel):
         # Un-initialise the AUI manager
         self.__auiMgr.UnInit()
 
+        # The AUI manager does not clear its
+        # reference to this panel, so let's
+        # do it here.
+        self.__auiMgr._frame  = None
         self.__profileManager = None
         self.__auiMgr         = None
 
+        fslpanel.FSLViewPanel.destroy(self)
+        
+
 
     def setCentrePanel(self, panel):
         panel.Reparent(self)
-- 
GitLab