JAL-3551 save structure viewer session refactorings, PyMol added
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 30 Apr 2020 10:50:27 +0000 (11:50 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 30 Apr 2020 10:50:27 +0000 (11:50 +0100)
13 files changed:
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/JmolCommands.java
src/jalview/ext/pymol/PymolCommands.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/gui/AppJmol.java
src/jalview/gui/AppJmolBinding.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/JalviewChimeraXBindingModel.java
src/jalview/gui/PymolBindingModel.java
src/jalview/gui/PymolViewer.java
src/jalview/gui/StructureViewerBase.java
src/jalview/project/Jalview2XML.java
src/jalview/structures/models/AAStructureBindingModel.java

index 4c19f6e..17433c5 100644 (file)
  */
 package jalview.ext.jmol;
 
-import jalview.api.FeatureRenderer;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SequenceI;
-import jalview.gui.IProgressIndicator;
-import jalview.gui.StructureViewer.ViewerType;
-import jalview.io.DataSourceType;
-import jalview.io.StructureFile;
-import jalview.structure.AtomSpec;
-import jalview.structure.StructureCommand;
-import jalview.structure.StructureCommandI;
-import jalview.structure.StructureSelectionManager;
-import jalview.structures.models.AAStructureBindingModel;
-
 import java.awt.Container;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
@@ -52,6 +39,19 @@ import org.jmol.api.JmolViewer;
 import org.jmol.c.CBK;
 import org.jmol.viewer.Viewer;
 
+import jalview.api.FeatureRenderer;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.IProgressIndicator;
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.DataSourceType;
+import jalview.io.StructureFile;
+import jalview.structure.AtomSpec;
+import jalview.structure.StructureCommand;
+import jalview.structure.StructureCommandI;
+import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.AAStructureBindingModel;
+
 public abstract class JalviewJmolBinding extends AAStructureBindingModel
         implements JmolStatusListener, JmolSelectionListener,
         ComponentListener
@@ -974,4 +974,16 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   {
     return String.valueOf(pdbfnum + 1);
   }
+
+  /**
+   * Returns ".spt" - the Jmol session file extension
+   * 
+   * @return
+   * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
+   */
+  @Override
+  public String getSessionFileExtension()
+  {
+    return ".spt";
+  }
 }
index bd44921..797c25b 100644 (file)
  */
 package jalview.ext.jmol;
 
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
@@ -36,12 +42,6 @@ import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.Comparison;
 
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
 /**
  * Routines for generating Jmol commands for Jalview/Jmol binding
  * 
@@ -372,10 +372,9 @@ public class JmolCommands extends StructureCommandsBase
   public StructureCommandI saveSession(String filepath)
   {
     /*
-     * https://chemapps.stolaf.edu/jmol/docs/#write
-     * not currently used in Jalview
+     * https://chemapps.stolaf.edu/jmol/docs/#writemodel
      */
-    return new StructureCommand("write \"" + filepath + "\"");
+    return new StructureCommand("write STATE \"" + filepath + "\"");
   }
 
   @Override
index 910aae1..115efa1 100644 (file)
@@ -1,14 +1,14 @@
 package jalview.ext.pymol;
 
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+
 import jalview.structure.AtomSpecModel;
 import jalview.structure.StructureCommand;
 import jalview.structure.StructureCommandI;
 import jalview.structure.StructureCommandsBase;
 
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * A class that generates commands to send to PyMol over its XML-RPC interface.
  * <p>
@@ -119,9 +119,8 @@ public class PymolCommands extends StructureCommandsBase
   @Override
   public StructureCommandI openCommandFile(String path)
   {
-    // where is this documented by PyMol?
-    // todo : xml-rpc answers 'method "@" is not supported'
-    return new StructureCommand("@" + path); // should be .pml
+    // https://pymolwiki.org/index.php/Run
+    return new StructureCommand("run", path); // should be .pml
   }
 
   @Override
index 3553d68..732fb16 100644 (file)
  */
 package jalview.ext.rbvi.chimera;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.BindException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
@@ -38,23 +54,6 @@ import jalview.structure.StructureCommandI;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.net.BindException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
-import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
-import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
-import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
-
 public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 {
   public static final String CHIMERA_FEATURE_GROUP = "Chimera";
@@ -864,10 +863,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Returns the file extension to use for a saved viewer session file
+   * Returns the file extension to use for a saved viewer session file (.py)
    * 
    * @return
    */
+  @Override
   public String getSessionFileExtension()
   {
     return ".py";
index 4087e63..581dc3d 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.AlignmentViewPanel;
-import jalview.bin.Cache;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SequenceI;
-import jalview.gui.StructureViewer.ViewerType;
-import jalview.structure.StructureCommand;
-import jalview.structures.models.AAStructureBindingModel;
-import jalview.util.BrowserLauncher;
-import jalview.util.ImageMaker;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-import jalview.ws.dbsources.Pdb;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Dimension;
@@ -50,6 +36,20 @@ import javax.swing.SwingUtilities;
 import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.structure.StructureCommand;
+import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.BrowserLauncher;
+import jalview.util.ImageMaker;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.ws.dbsources.Pdb;
+
 public class AppJmol extends StructureViewerBase
 {
   // ms to wait for Jmol to load files
@@ -666,12 +666,6 @@ public class AppJmol extends StructureViewerBase
   }
 
   @Override
-  public String getStateInfo()
-  {
-    return jmb == null ? null : jmb.jmolViewer.getStateInfo();
-  }
-
-  @Override
   public ViewerType getViewerType()
   {
     return ViewerType.JMOL;
index db698ac..34ff7b3 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.AlignmentViewPanel;
-import jalview.api.structures.JalviewStructureDisplayI;
-import jalview.bin.Cache;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SequenceI;
-import jalview.ext.jmol.JalviewJmolBinding;
-import jalview.io.DataSourceType;
-import jalview.structure.StructureSelectionManager;
-
 import java.awt.Container;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.util.Map;
 
 import javax.swing.JComponent;
@@ -38,6 +32,15 @@ import org.jmol.api.JmolAppConsoleInterface;
 import org.jmol.java.BS;
 import org.openscience.jmol.app.jmolpanel.console.AppConsole;
 
+import jalview.api.AlignmentViewPanel;
+import jalview.api.structures.JalviewStructureDisplayI;
+import jalview.bin.Cache;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.ext.jmol.JalviewJmolBinding;
+import jalview.io.DataSourceType;
+import jalview.structure.StructureSelectionManager;
+
 public class AppJmolBinding extends JalviewJmolBinding
 {
   public AppJmolBinding(AppJmol appJmol, StructureSelectionManager sSm,
@@ -165,4 +168,32 @@ public class AppJmolBinding extends JalviewJmolBinding
     // TODO Auto-generated method stub
     return null;
   }
+
+  /**
+   * Overrides the default method to save a session to file, in order to
+   * guarantee it is done synchronously. Jmol command 'write STATE path' would
+   * execute asynchronously, so instead we get the state and write it directly
+   * here.
+   */
+  @Override
+  protected void saveSession(File f)
+  {
+    String state = jmolViewer.getStateInfo();
+    if (state != null)
+    {
+      try
+      {
+        FileWriter fw = new FileWriter(f);
+        fw.write(state);
+        fw.close();
+      } catch (IOException e)
+      {
+        Cache.log.error("Error writing Jmol state: " + e.toString());
+      }
+    }
+    else
+    {
+      Cache.log.error("Error requesting Jmol state to save");
+    }
+  }
 }
index a6e479d..0d6a216 100644 (file)
@@ -604,77 +604,6 @@ public class ChimeraViewFrame extends StructureViewerBase
     return jmb;
   }
 
-  /**
-   * Ask Chimera to save its session to the designated file path, or to a
-   * temporary file if the path is null. Returns the file path if successful,
-   * else null.
-   * 
-   * @param filepath
-   * @see getStateInfo
-   */
-  protected String saveSession(String filepath)
-  {
-    String pathUsed = filepath;
-    try
-    {
-      if (pathUsed == null)
-      {
-        String suffix = jmb.getSessionFileExtension();
-        File tempFile = File.createTempFile("chimera", suffix);
-        tempFile.deleteOnExit();
-        pathUsed = tempFile.getPath();
-      }
-      boolean result = jmb.saveSession(pathUsed);
-      if (result)
-      {
-        this.chimeraSessionFile = pathUsed;
-        return pathUsed;
-      }
-    } catch (IOException e)
-    {
-    }
-    return null;
-  }
-
-  /**
-   * Returns a string representing the state of the Chimera session. This is
-   * done by requesting Chimera to save its session to a temporary file, then
-   * reading the file contents. Returns an empty string on any error.
-   */
-  @Override
-  public String getStateInfo()
-  {
-    String sessionFile = saveSession(null);
-    if (sessionFile == null)
-    {
-      return "";
-    }
-    InputStream is = null;
-    try
-    {
-      File f = new File(sessionFile);
-      byte[] bytes = new byte[(int) f.length()];
-      is = new FileInputStream(sessionFile);
-      is.read(bytes);
-      return new String(bytes);
-    } catch (IOException e)
-    {
-      return "";
-    } finally
-    {
-      if (is != null)
-      {
-        try
-        {
-          is.close();
-        } catch (IOException e)
-        {
-          // ignore
-        }
-      }
-    }
-  }
-
   @Override
   protected void fitToWindow_actionPerformed()
   {
index 3124fc1..d7b5a94 100644 (file)
@@ -1,5 +1,10 @@
 package jalview.gui;
 
+import java.util.List;
+
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.ext.rbvi.chimera.ChimeraXCommands;
@@ -8,12 +13,6 @@ import jalview.io.DataSourceType;
 import jalview.structure.StructureCommand;
 import jalview.structure.StructureSelectionManager;
 
-import java.util.List;
-
-import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
-import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
-import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
-
 public class JalviewChimeraXBindingModel extends JalviewChimeraBindingModel
 {
 
@@ -58,9 +57,10 @@ public class JalviewChimeraXBindingModel extends JalviewChimeraBindingModel
   }
 
   /**
-   * Returns the file extension to use for a saved viewer session file
+   * Returns the file extension to use for a saved viewer session file (.cxs)
    * 
    * @return
+   * @see https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html#sesformat
    */
   @Override
   public String getSessionFileExtension()
index af4afb0..6787c8a 100644 (file)
@@ -1,5 +1,10 @@
 package jalview.gui;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
@@ -11,11 +16,6 @@ import jalview.structure.StructureCommandI;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 public class PymolBindingModel extends AAStructureBindingModel
 {
   private PymolManager pymolManager;
@@ -72,7 +72,7 @@ public class PymolBindingModel extends AAStructureBindingModel
   protected List<String> executeCommand(StructureCommandI command,
           boolean getReply)
   {
-    System.out.println(command.toString()); // debug
+    // System.out.println(command.toString()); // debug
     return pymolManager.sendCommand(command, getReply);
   }
 
@@ -100,11 +100,6 @@ public class PymolBindingModel extends AAStructureBindingModel
     {
       pymolManager.exitPymol();
     }
-    // if (this.pymolListener != null)
-    // {
-    // pymolListener.shutdown();
-    // pymolListener = null;
-    // }
     pymolManager = null;
 
     if (pymolMonitor != null)
@@ -173,4 +168,16 @@ public class PymolBindingModel extends AAStructureBindingModel
     return file;
   }
 
+  /**
+   * Returns the file extension to use for a saved viewer session file (.pse)
+   * 
+   * @return
+   * @see https://pymolwiki.org/index.php/Save
+   */
+  @Override
+  public String getSessionFileExtension()
+  {
+    return ".pse";
+  }
+
 }
index 8f7f2c1..d0c9ea2 100644 (file)
@@ -1,5 +1,13 @@
 package jalview.gui;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JInternalFrame;
+import javax.swing.event.InternalFrameAdapter;
+import javax.swing.event.InternalFrameEvent;
+
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.bin.Cache;
@@ -11,14 +19,6 @@ import jalview.io.StructureFile;
 import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.JInternalFrame;
-import javax.swing.event.InternalFrameAdapter;
-import javax.swing.event.InternalFrameEvent;
-
 public class PymolViewer extends StructureViewerBase
 {
   private static final int myWidth = 500;
@@ -57,6 +57,40 @@ public class PymolViewer extends StructureViewerBase
     openNewPymol(ap, pe, seqs);
   }
 
+  /**
+   * Constructor given a session file to be restored
+   * 
+   * @param sessionFile
+   * @param alignPanel
+   * @param pdbArray
+   * @param seqsArray
+   * @param colourByPymol
+   * @param colourBySequence
+   * @param newViewId
+   */
+  public PymolViewer(String sessionFile, AlignmentPanel alignPanel,
+          PDBEntry[] pdbArray, SequenceI[][] seqsArray,
+          boolean colourByPymol, boolean colourBySequence, String newViewId)
+  {
+    // TODO convert to base/factory class method
+    this();
+    setViewId(newViewId);
+    this.pymolSessionFile = sessionFile;
+    openNewPymol(alignPanel, pdbArray, seqsArray);
+    if (colourByPymol)
+    {
+      binding.setColourBySequence(false);
+      seqColour.setSelected(false);
+      viewerColour.setSelected(true);
+    }
+    else if (colourBySequence)
+    {
+      binding.setColourBySequence(true);
+      seqColour.setSelected(true);
+      viewerColour.setSelected(false);
+    }
+  }
+
   private void openNewPymol(AlignmentPanel ap, PDBEntry[] pe,
           SequenceI[][] seqs)
   {
@@ -320,12 +354,6 @@ public class PymolViewer extends StructureViewerBase
   }
 
   @Override
-  public String getStateInfo()
-  {
-    return null;
-  }
-
-  @Override
   public ViewerType getViewerType()
   {
     return ViewerType.PYMOL;
index 25d9998..0c5c5f0 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.AlignmentViewPanel;
-import jalview.bin.Cache;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SequenceI;
-import jalview.gui.StructureViewer.ViewerType;
-import jalview.gui.ViewSelectionMenu.ViewSetProvider;
-import jalview.io.DataSourceType;
-import jalview.io.JalviewFileChooser;
-import jalview.io.JalviewFileView;
-import jalview.jbgui.GStructureViewer;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ColourSchemes;
-import jalview.structure.StructureMapping;
-import jalview.structures.models.AAStructureBindingModel;
-import jalview.util.MessageManager;
-import jalview.ws.dbsources.Pdb;
-
 import java.awt.Color;
 import java.awt.Component;
 import java.awt.event.ActionEvent;
@@ -64,6 +46,24 @@ import javax.swing.JRadioButtonMenuItem;
 import javax.swing.event.MenuEvent;
 import javax.swing.event.MenuListener;
 
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.gui.ViewSelectionMenu.ViewSetProvider;
+import jalview.io.DataSourceType;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
+import jalview.jbgui.GStructureViewer;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemes;
+import jalview.structure.StructureMapping;
+import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.MessageManager;
+import jalview.ws.dbsources.Pdb;
+
 /**
  * Base class with common functionality for JMol, Chimera or other structure
  * viewers.
@@ -198,8 +198,6 @@ public abstract class StructureViewerBase extends GStructureViewer
     this.viewId = viewId;
   }
 
-  public abstract String getStateInfo();
-
   protected void buildActionMenu()
   {
     if (_alignwith == null)
@@ -1141,4 +1139,16 @@ public abstract class StructureViewerBase extends GStructureViewer
     return filePath;
   }
 
+  /**
+   * If supported, saves the state of the structure viewer to a temporary file
+   * and returns the file, else returns null
+   * 
+   * @return
+   */
+  public File saveSession()
+  {
+    // TODO: a wait loop to ensure the file is written fully before returning?
+    return getBinding() == null ? null : getBinding().saveSession();
+  }
+
 }
index df4a93e..e7af837 100644 (file)
@@ -24,6 +24,55 @@ import static jalview.math.RotatableMatrix.Axis.X;
 import static jalview.math.RotatableMatrix.Axis.Y;
 import static jalview.math.RotatableMatrix.Axis.Z;
 
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Rectangle;
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+
+import javax.swing.JInternalFrame;
+import javax.swing.SwingUtilities;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.Marshaller;
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+
 import jalview.analysis.Conservation;
 import jalview.analysis.PCA;
 import jalview.analysis.scoremodels.ScoreModels;
@@ -64,6 +113,7 @@ import jalview.gui.JvOptionPane;
 import jalview.gui.OOMWarning;
 import jalview.gui.PCAPanel;
 import jalview.gui.PaintRefresher;
+import jalview.gui.PymolViewer;
 import jalview.gui.SplitFrame;
 import jalview.gui.StructureViewer;
 import jalview.gui.StructureViewer.ViewerType;
@@ -150,54 +200,6 @@ import jalview.xml.binding.jalview.SequenceSet.SequenceSetProperties;
 import jalview.xml.binding.jalview.ThresholdType;
 import jalview.xml.binding.jalview.VAMSAS;
 
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.Rectangle;
-import java.io.BufferedReader;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.math.BigInteger;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.Vector;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.jar.JarOutputStream;
-
-import javax.swing.JInternalFrame;
-import javax.swing.SwingUtilities;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBElement;
-import javax.xml.bind.Marshaller;
-import javax.xml.datatype.DatatypeConfigurationException;
-import javax.xml.datatype.DatatypeFactory;
-import javax.xml.datatype.XMLGregorianCalendar;
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamReader;
-
 /**
  * Write out the current jalview desktop state as a Jalview XML stream.
  * 
@@ -1087,15 +1089,17 @@ public class Jalview2XML
               if (!storeDS && !viewIds.contains(viewId))
               {
                 viewIds.add(viewId);
-                try
+                File viewerState = viewFrame.saveSession();
+                if (viewerState != null)
                 {
-                  String viewerState = viewFrame.getStateInfo();
-                  writeJarEntry(jout, getViewerJarEntryName(viewId),
-                          viewerState.getBytes());
-                } catch (IOException e)
+                  copyFileToJar(jout, viewerState.getPath(),
+                          getViewerJarEntryName(viewId));
+                }
+                else
                 {
-                  System.err.println(
-                          "Error saving viewer state: " + e.getMessage());
+                  Cache.log.error("Failed to save viewer state for "
+                          +
+                          viewFrame.getViewerType().toString());
                 }
               }
             }
@@ -1992,32 +1996,23 @@ public class Jalview2XML
   protected void copyFileToJar(JarOutputStream jout, String infilePath,
           String jarEntryName)
   {
-    DataInputStream dis = null;
-    try
+    try (InputStream is = new FileInputStream(infilePath))
     {
       File file = new File(infilePath);
       if (file.exists() && jout != null)
       {
-        dis = new DataInputStream(new FileInputStream(file));
-        byte[] data = new byte[(int) file.length()];
-        dis.readFully(data);
-        writeJarEntry(jout, jarEntryName, data);
+        System.out.println("Writing jar entry " + jarEntryName);
+        jout.putNextEntry(new JarEntry(jarEntryName));
+        copyAll(is, jout);
+        jout.closeEntry();
+        // dis = new DataInputStream(new FileInputStream(file));
+        // byte[] data = new byte[(int) file.length()];
+        // dis.readFully(data);
+        // writeJarEntry(jout, jarEntryName, data);
       }
     } catch (Exception ex)
     {
       ex.printStackTrace();
-    } finally
-    {
-      if (dis != null)
-      {
-        try
-        {
-          dis.close();
-        } catch (IOException e)
-        {
-          // ignore
-        }
-      }
     }
   }
 
@@ -2044,6 +2039,24 @@ public class Jalview2XML
   }
 
   /**
+   * Copies input to output, in 4K buffers; handles any data (text or binary)
+   * 
+   * @param in
+   * @param out
+   * @throws IOException
+   */
+  protected void copyAll(InputStream in, OutputStream out)
+          throws IOException
+  {
+    byte[] buffer = new byte[4096];
+    int bytesRead = 0;
+    while ((bytesRead = in.read(buffer)) != -1)
+    {
+      out.write(buffer, 0, bytesRead);
+    }
+  }
+
+  /**
    * Save the state of a structure viewer
    * 
    * @param ap
@@ -3171,8 +3184,6 @@ public class Jalview2XML
   protected String copyJarEntry(jarInputStreamProvider jprovider,
           String jarEntryName, String prefix, String suffixModel)
   {
-    BufferedReader in = null;
-    PrintWriter out = null;
     String suffix = ".tmp";
     if (suffixModel == null)
     {
@@ -3183,33 +3194,24 @@ public class Jalview2XML
     {
       suffix = "." + suffixModel.substring(sfpos + 1);
     }
-    try
-    {
-      JarInputStream jin = jprovider.getJarInputStream();
-      /*
-       * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
-       * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
-       * FileInputStream(jprovider)); }
-       */
 
+    try (JarInputStream jin = jprovider.getJarInputStream())
+    {
       JarEntry entry = null;
       do
       {
         entry = jin.getNextJarEntry();
       } while (entry != null && !entry.getName().equals(jarEntryName));
+
       if (entry != null)
       {
-        in = new BufferedReader(new InputStreamReader(jin, UTF_8));
+        // in = new BufferedReader(new InputStreamReader(jin, UTF_8));
         File outFile = File.createTempFile(prefix, suffix);
         outFile.deleteOnExit();
-        out = new PrintWriter(new FileOutputStream(outFile));
-        String data;
-
-        while ((data = in.readLine()) != null)
+        try (OutputStream os = new FileOutputStream(outFile))
         {
-          out.println(data);
+          copyAll(jin, os);
         }
-        out.flush();
         String t = outFile.getAbsolutePath();
         return t;
       }
@@ -3220,22 +3222,6 @@ public class Jalview2XML
     } catch (Exception ex)
     {
       ex.printStackTrace();
-    } finally
-    {
-      if (in != null)
-      {
-        try
-        {
-          in.close();
-        } catch (IOException e)
-        {
-          // ignore
-        }
-      }
-      if (out != null)
-      {
-        out.close();
-      }
     }
 
     return null;
@@ -4414,10 +4400,15 @@ public class Jalview2XML
      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
      * "viewer_"+stateData.viewId
      */
-    if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
+    String viewerType = stateData.getType();
+    if (ViewerType.CHIMERA.toString().equals(viewerType))
     {
       createChimeraViewer(viewerData, af, jprovider);
     }
+    if (ViewerType.PYMOL.toString().equals(viewerType))
+    {
+      createPymolViewer(viewerData, af, jprovider);
+    }
     else
     {
       /*
@@ -6335,6 +6326,61 @@ public class Jalview2XML
   }
 
   /**
+   * Create a new PyMol viewer
+   * 
+   * @param data
+   * @param af
+   * @param jprovider
+   */
+  protected void createPymolViewer(
+          Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+          jarInputStreamProvider jprovider)
+  {
+    StructureViewerModel data = viewerData.getValue();
+    String pymolSessionFile = data.getStateData();
+  
+    /*
+     * Copy PyMol session from jar entry "viewer_"+viewId to a temporary file
+     * 
+     * NB this is the 'saved' viewId as in the project file XML, _not_ the
+     * 'uniquified' sviewid used to reconstruct the viewer here
+     */
+    String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
+    pymolSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
+            "pymol", ".pse");
+  
+    Set<Entry<File, StructureData>> fileData = data.getFileData()
+            .entrySet();
+    List<PDBEntry> pdbs = new ArrayList<>();
+    List<SequenceI[]> allseqs = new ArrayList<>();
+    for (Entry<File, StructureData> pdb : fileData)
+    {
+      String filePath = pdb.getValue().getFilePath();
+      String pdbId = pdb.getValue().getPdbId();
+      // pdbs.add(new PDBEntry(filePath, pdbId));
+      pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
+      final List<SequenceI> seqList = pdb.getValue().getSeqList();
+      SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
+      allseqs.add(seqs);
+    }
+  
+    boolean colourByPymol = data.isColourByViewer();
+    boolean colourBySequence = data.isColourWithAlignPanel();
+  
+    // TODO use StructureViewer as a factory here, see JAL-1761
+    final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
+    final SequenceI[][] seqsArray = allseqs
+            .toArray(new SequenceI[allseqs.size()][]);
+    String newViewId = viewerData.getKey();
+  
+    PymolViewer pv = new PymolViewer(pymolSessionFile,
+            af.alignPanel, pdbArray, seqsArray, colourByPymol,
+            colourBySequence, newViewId);
+    pv.setSize(data.getWidth(), data.getHeight());
+    pv.setLocation(data.getX(), data.getY());
+  }
+
+  /**
    * Populates an XML model of the feature colour scheme for one feature type
    * 
    * @param featureType
index db523f9..7899c40 100644 (file)
  */
 package jalview.structures.models;
 
+import java.awt.Color;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.SwingUtilities;
+
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.StructureSelectionManagerProvider;
 import jalview.api.structures.JalviewStructureDisplayI;
+import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
@@ -46,17 +60,6 @@ import jalview.structure.StructureSelectionManager;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
 
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.swing.SwingUtilities;
-
 /**
  * 
  * A base class to hold common function for protein structure model binding.
@@ -1513,4 +1516,55 @@ public abstract class AAStructureBindingModel
   
     atomSpec.addRange(model, startPos, endPos, chain);
   }
+
+  /**
+   * Returns the file extension (including '.' separator) to use for a saved
+   * viewer session file. Default is to return null (not supported), override as
+   * required.
+   * 
+   * @return
+   */
+  public String getSessionFileExtension()
+  {
+    return null;
+  }
+
+  /**
+   * If supported, saves the state of the structure viewer to a temporary file
+   * and returns the file. Returns null and logs an error on any failure.
+   * 
+   * @return
+   */
+  public File saveSession()
+  {
+    String prefix = getViewerType().toString();
+    String suffix = getSessionFileExtension();
+    File f = null;
+    try
+    {
+      f = File.createTempFile(prefix, suffix);
+      saveSession(f);
+    } catch (IOException e)
+    {
+      Cache.log.error(String.format("Error saving %s session: %s",
+              prefix, e.toString()));
+    }
+
+    return f;
+  }
+
+  /**
+   * Saves the structure viewer session to the given file
+   * 
+   * @param f
+   */
+  protected void saveSession(File f)
+  {
+    StructureCommandI cmd = commandGenerator
+            .saveSession(f.getPath());
+    if (cmd != null)
+    {
+      executeCommand(cmd, false);
+    }
+  }
 }