From bc3bfd3d5edf06442aa9ccea3f88082b7247536e Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Thu, 16 Jul 2015 14:50:28 +0100
Subject: [PATCH] SliceCanvas/LightBoxCanvas now have destroy methods which are
 explciitly called by their owning OrthoPanel/LightBoxPanels

---
 fsl/fslview/displaycontext/displaycontext.py |  2 +
 fsl/fslview/frame.py                         |  3 +-
 fsl/fslview/gl/lightboxcanvas.py             | 16 +++++
 fsl/fslview/gl/slicecanvas.py                | 73 ++++++++++++++++----
 fsl/fslview/gl/wxgllightboxcanvas.py         | 57 ---------------
 fsl/fslview/gl/wxglslicecanvas.py            | 51 +-------------
 fsl/fslview/views/lightboxpanel.py           |  2 +
 fsl/fslview/views/orthopanel.py              |  7 +-
 8 files changed, 90 insertions(+), 121 deletions(-)

diff --git a/fsl/fslview/displaycontext/displaycontext.py b/fsl/fslview/displaycontext/displaycontext.py
index 8ab23f27e..71c45aae7 100644
--- a/fsl/fslview/displaycontext/displaycontext.py
+++ b/fsl/fslview/displaycontext/displaycontext.py
@@ -97,6 +97,8 @@ class DisplayContext(props.SyncableHasProperties):
         
     def destroy(self):
 
+        self.__overlayList.removeListener('overlays', self.__name)
+
         for overlay, display in self.__displays.items():
             display.destroy()
 
diff --git a/fsl/fslview/frame.py b/fsl/fslview/frame.py
index 4bd7990b2..440624556 100644
--- a/fsl/fslview/frame.py
+++ b/fsl/fslview/frame.py
@@ -202,7 +202,8 @@ class FSLViewFrame(wx.Frame):
             menuBar.Remove(menuIdx)
 
         # Calling fslpanel.FSLViewPanel.destroy()
-        # - I think that the AUINotebook does the
+        # and DisplayContext.destroy() - the
+        # AUINotebook should do the
         # wx.Window.Destroy side of things ...
         panel.destroy()
         dctx .destroy()
diff --git a/fsl/fslview/gl/lightboxcanvas.py b/fsl/fslview/gl/lightboxcanvas.py
index e3b08e174..219058c1c 100644
--- a/fsl/fslview/gl/lightboxcanvas.py
+++ b/fsl/fslview/gl/lightboxcanvas.py
@@ -212,6 +212,22 @@ class LightBoxCanvas(slicecanvas.SliceCanvas):
                          '{}_zPosChanged'.format(self.name),
                          self._zPosChanged)
 
+    def destroy(self):
+
+        self.removeListener('pos', '{}_zPosChanged'.format(self.name))
+        self.removeListener('sliceSpacing',                self.name)
+        self.removeListener('ncols',          self.name)
+        self.removeListener('nrows',          self.name)
+        self.removeListener('zrange',         self.name)
+        self.removeListener('showGridLines',  self.name)
+        self.removeListener('highlightSlice', self.name)
+        self.removeListener('topRow',         self.name)
+        
+        if self._offscreenRenderTexture is not None:
+            self._offscreenRenderTexture.destroy()
+        
+        slicecanvas.SliceCanvas.destroy(self)
+
         
     def _topRowChanged(self, *a):
         """Called when the :attr:`topRow` property changes.  Adjusts display
diff --git a/fsl/fslview/gl/slicecanvas.py b/fsl/fslview/gl/slicecanvas.py
index e50a14797..6da930479 100644
--- a/fsl/fslview/gl/slicecanvas.py
+++ b/fsl/fslview/gl/slicecanvas.py
@@ -308,6 +308,47 @@ class SliceCanvas(props.HasProperties):
                                      self._overlayBoundsChanged)
 
 
+    def destroy(self):
+        """This method must be called when this ``SliceCanvas`` is no longer
+        being used.
+
+        It removes listeners from all :class:`.OverlayList`,
+        :class:`.DisplayContext`, and :class:`.Display` instances, and
+        destroys OpenGL representations of all overlays.
+        """
+        self.removeListener('zax',           self.name)
+        self.removeListener('pos',           self.name)
+        self.removeListener('displayBounds', self.name)
+        self.removeListener('showCursor',    self.name)
+        self.removeListener('invertX',       self.name)
+        self.removeListener('invertY',       self.name)
+        self.removeListener('zoom',          self.name)
+        self.removeListener('renderMode',    self.name)
+
+        self.overlayList.removeListener('overlays',     self.name)
+        self.displayCtx .removeListener('bounds',       self.name)
+        self.displayCtx .removeListener('overlayOrder', self.name)
+
+        for overlay in self.overlayList:
+            disp  = self.displayCtx.getDisplay(overlay)
+            globj = self._glObjects[overlay]
+
+            disp.removeListener('overlayType',  self.name)
+            disp.removeListener('enabled',      self.name)
+            disp.unbindProps(   'softwareMode', self)
+
+            globj.destroy()
+
+            rt, rtName = self._prerenderTextures.get(overlay, (None, None))
+            ot         = self._offscreenTextures.get(overlay, None)
+
+            if rt is not None: glresources.delete(rtName)
+            if ot is not None: ot         .destroy()
+
+        self.overlayList = None
+        self.displayCxt  = None
+            
+
     def _initGL(self):
         """Call the _overlayListChanged method - it will generate
         any necessary GL data for each of the overlays
@@ -516,7 +557,7 @@ class SliceCanvas(props.HasProperties):
         self._refresh()
 
 
-    def __genGLObject(self, overlay, display, updateRenderTextures=True):
+    def __genGLObject(self, overlay, updateRenderTextures=True):
         """Creates a :class:`.GLObject` instance for the given ``overlay``,
         destroying any existing instance.
 
@@ -525,6 +566,8 @@ class SliceCanvas(props.HasProperties):
         render texture associated with the overlay is destroyed.
         """
 
+        display = self.displayCtx.getDisplay(overlay)
+
         # Tell the previous GLObject (if
         # any) to clean up after itself
         globj = self._glObjects.pop(overlay, None)
@@ -559,6 +602,19 @@ class SliceCanvas(props.HasProperties):
 
         self._glObjects[overlay] = globj
 
+        if not display.isBound('softwareMode', self):
+            display.bindProps('softwareMode', self)
+
+        display.addListener('overlayType',
+                            self.name,
+                            self.__overlayTypeChanged,
+                            overwrite=True)
+
+        display.addListener('enabled',
+                            self.name,
+                            self._refresh,
+                            overwrite=True)
+
         return globj
  
             
@@ -590,18 +646,7 @@ class SliceCanvas(props.HasProperties):
             if overlay in self._glObjects:
                 continue
 
-            display = self.displayCtx.getDisplay(overlay)
-
-            self.__genGLObject(overlay, display, updateRenderTextures=False)
-
-            # Bind Display.softwareMode to SliceCanvas.softwareMode
-            display.bindProps('softwareMode', self)
-
-            display.addListener('overlayType',
-                                self.name,
-                                self.__overlayTypeChanged)
-            
-            display.addListener('enabled', self.name, self._refresh)
+            self.__genGLObject(overlay, updateRenderTextures=False)
 
         self._updateRenderTextures()
         self._resolutionLimitChange()
@@ -919,7 +964,7 @@ class SliceCanvas(props.HasProperties):
                 continue
             
             if globj is None:
-                globj = self.__genGLObject(overlay, display)
+                globj = self.__genGLObject(overlay)
 
             # On-screen rendering - the globject is
             # rendered directly to the screen canvas
diff --git a/fsl/fslview/gl/wxgllightboxcanvas.py b/fsl/fslview/gl/wxgllightboxcanvas.py
index 60a9074c2..75d1fe158 100644
--- a/fsl/fslview/gl/wxgllightboxcanvas.py
+++ b/fsl/fslview/gl/wxgllightboxcanvas.py
@@ -8,17 +8,12 @@
 :class:`.LightBoxCanvas`, and a :class:`wx.glcanvas.GLCanvas`.
 """
 
-import logging
 
 import wx
 import wx.glcanvas              as wxgl
 
 import lightboxcanvas           as lightboxcanvas
 import fsl.fslview.gl           as fslgl
-import fsl.fslview.gl.resources as glresources
-
-
-log = logging.getLogger(__name__)
 
 
 class WXGLLightBoxCanvas(lightboxcanvas.LightBoxCanvas,
@@ -41,58 +36,6 @@ class WXGLLightBoxCanvas(lightboxcanvas.LightBoxCanvas,
                                                displayCtx,
                                                zax)
         fslgl.WXGLCanvasTarget       .__init__(self)
-        
-        # the overlay list is probably going to outlive
-        # this SliceCanvas object, so we do the right
-        # thing and remove our listeners when we die
-        def onDestroy(ev):
-            ev.Skip()
-
-            if ev.GetEventObject() is not self:
-                return
-
-            self.removeListener('zax',            self.name)
-            self.removeListener('pos',            self.name)
-            self.removeListener('pos',
-                                '{}_zPosChanged'.format(self.name))
-            self.removeListener('displayBounds',  self.name)
-            self.removeListener('showCursor',     self.name)
-            self.removeListener('invertX',        self.name)
-            self.removeListener('invertY',        self.name)
-            self.removeListener('zoom',           self.name)
-            self.removeListener('sliceSpacing',   self.name)
-            self.removeListener('ncols',          self.name)
-            self.removeListener('nrows',          self.name)
-            self.removeListener('zrange',         self.name)
-            self.removeListener('showGridLines',  self.name)
-            self.removeListener('highlightSlice', self.name)
-            self.removeListener('topRow',         self.name)
-            self.removeListener('renderMode',     self.name)
-
-            self.overlayList.removeListener('overlays',     self.name)
-            self.displayCtx .removeListener('bounds',       self.name)
-            self.displayCtx .removeListener('overlayOrder', self.name)
-            
-            for overlay in self.overlayList:
-                
-                disp  = self.displayCtx.getDisplay(overlay)
-                globj = self._glObjects[overlay]
-
-                disp.removeListener('overlayType',   self.name)
-                disp.removeListener('enabled',       self.name)
-                disp.removeListener('softwareMode',  self.name)
-
-                globj.destroy()
-
-                rt, rtName = self._prerenderTextures.get(overlay, (None, None))
-
-                if rt is not None:
-                    glresources.delete(rtName)
-
-            if self._offscreenRenderTexture is not None:
-                self._offscreenRenderTexture.destroy()
-
-        self.Bind(wx.EVT_WINDOW_DESTROY, onDestroy)
 
         # When the canvas is resized, we have to update
         # the display bounds to preserve the aspect ratio
diff --git a/fsl/fslview/gl/wxglslicecanvas.py b/fsl/fslview/gl/wxglslicecanvas.py
index ad0778e06..502679b7f 100644
--- a/fsl/fslview/gl/wxglslicecanvas.py
+++ b/fsl/fslview/gl/wxglslicecanvas.py
@@ -13,17 +13,12 @@ data (although most of the functionality is provided by the
 :class:`.SliceCanvas` class).
 """
 
-import logging
 
 import wx
-import wx.glcanvas              as wxgl
+import wx.glcanvas    as wxgl
 
-import slicecanvas              as slicecanvas
-import fsl.fslview.gl.resources as glresources
-import fsl.fslview.gl           as fslgl
-
-
-log = logging.getLogger(__name__)
+import slicecanvas    as slicecanvas
+import fsl.fslview.gl as fslgl
 
 
 class WXGLSliceCanvas(slicecanvas.SliceCanvas,
@@ -43,46 +38,6 @@ class WXGLSliceCanvas(slicecanvas.SliceCanvas,
         wxgl.GLCanvas          .__init__(self, parent)
         slicecanvas.SliceCanvas.__init__(self, overlayList, displayCtx, zax)
         fslgl.WXGLCanvasTarget .__init__(self)
-        
-        # the image list is probably going to outlive
-        # this SliceCanvas object, so we do the right
-        # thing and remove our listeners when we die
-        def onDestroy(ev):
-            ev.Skip()
-
-            if ev.GetEventObject() is not self:
-                return
-
-            self.removeListener('zax',           self.name)
-            self.removeListener('pos',           self.name)
-            self.removeListener('displayBounds', self.name)
-            self.removeListener('showCursor',    self.name)
-            self.removeListener('invertX',       self.name)
-            self.removeListener('invertY',       self.name)
-            self.removeListener('zoom',          self.name)
-            self.removeListener('renderMode',    self.name)
-            
-            self.overlayList.removeListener('overlays',     self.name)
-            self.displayCtx .removeListener('bounds',       self.name)
-            self.displayCtx .removeListener('overlayOrder', self.name)
-            
-            for overlay in self.overlayList:
-                disp  = self.displayCtx.getDisplay(overlay)
-                globj = self._glObjects[overlay]
-                    
-                disp.removeListener('overlayType',  self.name)
-                disp.removeListener('enabled',      self.name)
-                disp.removeListener('softwareMode', self.name)
-
-                globj.destroy()
-
-                rt, rtName = self._prerenderTextures.get(overlay, (None, None))
-                ot         = self._offscreenTextures.get(overlay, None)
-
-                if rt is not None: glresources.delete(rtName)
-                if ot is not None: ot   .destroy()
-
-        self.Bind(wx.EVT_WINDOW_DESTROY, onDestroy)
 
         # When the canvas is resized, we have to update
         # the display bounds to preserve the aspect ratio
diff --git a/fsl/fslview/views/lightboxpanel.py b/fsl/fslview/views/lightboxpanel.py
index 54178ab81..616e3ee7a 100644
--- a/fsl/fslview/views/lightboxpanel.py
+++ b/fsl/fslview/views/lightboxpanel.py
@@ -138,6 +138,8 @@ class LightBoxPanel(canvaspanel.CanvasPanel):
         self._displayCtx .removeListener('selectedOverlay', self._name)
         self._overlayList.removeListener('overlays',        self._name)
 
+        self._lbCanvas.destroy()
+
         canvaspanel.CanvasPanel.destroy(self)
 
         
diff --git a/fsl/fslview/views/orthopanel.py b/fsl/fslview/views/orthopanel.py
index d503048ac..567f29e84 100644
--- a/fsl/fslview/views/orthopanel.py
+++ b/fsl/fslview/views/orthopanel.py
@@ -184,7 +184,6 @@ class OrthoPanel(canvaspanel.CanvasPanel):
         this OrthoPanel. So when this panel is destroyed, all those
         registered listeners are removed.
         """
-        canvaspanel.CanvasPanel.destroy(self)
 
         self._displayCtx .removeListener('location',        self._name)
         self._displayCtx .removeListener('bounds',          self._name)
@@ -192,6 +191,10 @@ class OrthoPanel(canvaspanel.CanvasPanel):
         self._displayCtx .removeListener('overlayOrder',    self._name)
         self._overlayList.removeListener('overlays',        self._name)
 
+        self._xcanvas.destroy()
+        self._ycanvas.destroy()
+        self._zcanvas.destroy()
+
         # The _overlayListChanged method adds
         # listeners to individual overlays,
         # so we have to remove them too
@@ -199,6 +202,8 @@ class OrthoPanel(canvaspanel.CanvasPanel):
             opts = self._displayCtx.getOpts(ovl)
             opts.removeGlobalListener(self._name)
 
+        canvaspanel.CanvasPanel.destroy(self)
+
             
     def __onZoom(self, *a):
         """Called when the :attr:`.SceneOpts.zoom` property changes.
-- 
GitLab