From cb2d713a36f0f592be0ccbcb839caa72fc6a59b5 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauld.mccarthy@gmail.com>
Date: Sat, 16 Jan 2016 13:43:38 +0000
Subject: [PATCH] Bugfix in perspective serialisation - view panels need to be
 renamed so their indices match. FSLEyesFrame has better control over
 save/restore layout.

---
 fsl/fsleyes/frame.py             | 16 +++++---
 fsl/fsleyes/fsleyes_parseargs.py | 12 +++---
 fsl/fsleyes/perspectives.py      | 65 +++++++++++++++++++++++++++++++-
 fsl/tools/fsleyes.py             | 10 +++--
 4 files changed, 86 insertions(+), 17 deletions(-)

diff --git a/fsl/fsleyes/frame.py b/fsl/fsleyes/frame.py
index fa25c8afe..ea6675c3d 100644
--- a/fsl/fsleyes/frame.py
+++ b/fsl/fsleyes/frame.py
@@ -86,7 +86,8 @@ class FSLEyesFrame(wx.Frame):
                  parent,
                  overlayList,
                  displayCtx,
-                 restore=False):
+                 restore=False,
+                 save=True):
         """Create a ``FSLEyesFrame``.
 
         .. note:: The ``restore`` functionality is not currently implemented.
@@ -101,6 +102,8 @@ class FSLEyesFrame(wx.Frame):
         
         :arg restore:     Restores previous saved layout. If ``False``, no 
                           view panels will be displayed.
+
+        :arg save:        Save current layout when closed. 
         """
         
         wx.Frame.__init__(self, parent, title='FSLeyes')
@@ -163,10 +166,7 @@ class FSLEyesFrame(wx.Frame):
         self.__makeMenuBar()
         self.__restoreState(restore)
 
-        # If we have not restored the previous
-        # layout, we are not going to save the
-        # layout on exit.
-        self.__saveLayout = restore
+        self.__saveLayout = save
 
         self.__auiManager.Bind(aui.EVT_AUI_PANE_CLOSE, self.__onViewPanelClose)
         self             .Bind(wx.EVT_CLOSE,           self.__onClose)
@@ -468,6 +468,11 @@ class FSLEyesFrame(wx.Frame):
             size     = self.GetSize().Get()
             position = self.GetScreenPosition().Get()
             layout   = perspectives.serialisePerspective(self)
+
+
+            log.debug('Saving size: {}'    .format(size))
+            log.debug('Saving position: {}'.format(position))
+            log.debug('Saving layout: {}'  .format(layout))
             
             fslsettings.write('framesize',     str(size))
             fslsettings.write('frameposition', str(position))
@@ -592,6 +597,7 @@ class FSLEyesFrame(wx.Frame):
             if layout is None:
                 perspectives.loadPerspective(self, 'default')
             else:
+                log.debug('Restoring previous layout: {}'.format(layout))
                 perspectives.applyPerspective(
                     self,
                     'framelayout',
diff --git a/fsl/fsleyes/fsleyes_parseargs.py b/fsl/fsleyes/fsleyes_parseargs.py
index 7d67acf4d..83be91f82 100644
--- a/fsl/fsleyes/fsleyes_parseargs.py
+++ b/fsl/fsleyes/fsleyes_parseargs.py
@@ -416,14 +416,14 @@ defines descriptions for ecah command line group.
 # Descriptions for each group
 GROUPDESCS = td.TypeDict({
 
-    'SceneOpts'    : 'These settings are only applied if the \'--scene\' '
-                     'option is set to \'lightbox\' or \'ortho\'.',
+    'SceneOpts'    : 'These settings are applied to every '
+                     'orthographic and lightbox view.',
 
-    'OrthoOpts'    : 'These settings are only applied if the \'--scene\' '
-                     'option is set to \'ortho\'.', 
+    'OrthoOpts'    : 'These settings are applied to every '
+                     'ortho view.', 
 
-    'LightBoxOpts' : 'These settings are only applied if the \'--scene\' '
-                     'option is set to \'lightbox\'.',
+    'LightBoxOpts' : 'These settings are applied to every '
+                     'lightbox view.',
  
     'Display'      : 'Each display option will be applied to the '
                      'overlay which is listed before that option. '
diff --git a/fsl/fsleyes/perspectives.py b/fsl/fsleyes/perspectives.py
index 5c1e83d4b..6ddda36fc 100644
--- a/fsl/fsleyes/perspectives.py
+++ b/fsl/fsleyes/perspectives.py
@@ -252,7 +252,32 @@ def serialisePerspective(frame):
     # AuiManager, and makes sure that the order of the
     # child pane layout specifications in the string is
     # the same as the order of the children in the list.
-    def patchLayoutString(auiMgr, panels):
+    #
+    # If the 'rename' argument is True, this function
+    # performs an additional step.
+    #
+    # The FSLEyesFrame gives each of its view panels a
+    # unique name of the form "ClassName index", where
+    # the 'index' is a sequentially increasing identifier
+    # number (so that multiple views of the same type can
+    # be differentiated). If the 'rename' argument to
+    # this function is True, these names are adjusted so
+    # that they begin at 1 and increase sequentially. This
+    # is done by the patchPanelName function, defined
+    # below.
+    #
+    # This name adjustment is required to handle
+    # situations where the indices of existing view panels
+    # are not sequential, as when a layout is applied, the
+    # view panel names given by the FSLEyesFrame must
+    # match the names that are specified in the layout
+    # perspective string.
+    #
+    # In addition to patching the name of each panel,
+    # the 'rename' argument will also cause the panel
+    # caption (its display title) to be adjusted so that
+    # it is in line with the name.
+    def patchLayoutString(auiMgr, panels, rename=False):
 
         layoutStr = auiMgr.SavePerspective()
 
@@ -276,12 +301,48 @@ def serialisePerspective(frame):
                 pi          += 1
                 sections[si] = panelLayout
 
+                if rename:
+                    sections[si] = patchPanelName(sections[si], pi)
+
         # Now the panel layouts in our layout string
         # are in the same order as our list of view
         # panels - we can re-join the layout string
         # sections, and we're done.
         return '|'.join(sections) + '|'
 
+    # The purpose of this function is described above.
+    def patchPanelName(layoutString, index):
+        # In each AUI layout section, 'key=value'
+        # pairs are separated with a semi-colon
+        kvps = layoutString.split(';')
+
+        # And each 'key=value' pair is separated
+        # with an equals character
+        kvps = [kvp.split('=') for kvp in kvps]
+        kvps = collections.OrderedDict(kvps)
+
+        # We need to update the indices contained
+        # in the 'name' and 'caption' values
+        name    = kvps['name']
+        caption = kvps['caption']
+
+        # Strip off the old index
+        name    = ' '.join(name   .split()[:-1])
+        caption = ' '.join(caption.split()[:-1])
+
+        # Patch in the new index
+        name    = '{} {}'.format(name,    index)
+        caption = '{} {}'.format(caption, index)
+
+        kvps['name']    = name
+        kvps['caption'] = caption
+
+        # Reconstruct the layout string
+        kvps = ['='.join((k, v)) for k, v in kvps.items()]
+        kvps = ';'.join(kvps)
+
+        return kvps
+                                      
     # Now we can start extracting the layout information.
     # We start with the FSLEyesFrame layout.
     auiMgr     = frame.getAuiManager()
@@ -289,7 +350,7 @@ def serialisePerspective(frame):
 
     # Generate the frame layout string, and a
     # list of the children of the frame
-    frameLayout   = patchLayoutString(auiMgr, viewPanels)
+    frameLayout   = patchLayoutString(auiMgr, viewPanels, True)
     frameChildren = [type(vp).__name__ for vp in viewPanels]
     frameChildren = ','.join(frameChildren)
 
diff --git a/fsl/tools/fsleyes.py b/fsl/tools/fsleyes.py
index 3c7aa894d..bf9aa73af 100644
--- a/fsl/tools/fsleyes.py
+++ b/fsl/tools/fsleyes.py
@@ -178,14 +178,16 @@ def interface(parent, args, ctx):
 
     # If a scene or perspective has not been
     # specified, the default behaviour is to
-    # restore the previous frame layout.
-    if args.scene is None and args.perspective is None: restore = True
-    else:                                               restore = False
+    # restore the previous frame layout. And
+    # if a scene is specified, the layout is
+    # not saved on exit.
+    restore = args.scene is None and args.perspective is None
+    save    = args.scene is None
 
     status.update('Creating FSLeyes interface...')
     
     frame = fsleyesframe.FSLEyesFrame(
-        parent, overlayList, displayCtx, restore)
+        parent, overlayList, displayCtx, restore, save)
 
     # Make sure the new frame is shown
     # before destroying the splash screen
-- 
GitLab