JAL-3619 default silently to Jmol if viewerType absent (Jalview < 2.9)
[jalview.git] / src / jalview / project / Jalview2XML.java
index 8c3175f..b408c07 100644 (file)
  */
 package jalview.project;
 
+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;
+import jalview.analysis.scoremodels.SimilarityParams;
 import jalview.api.FeatureColourI;
 import jalview.api.ViewStyleI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.GeneLocus;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.PDBEntry;
+import jalview.datamodel.Point;
 import jalview.datamodel.RnaViewerModel;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
@@ -41,26 +102,32 @@ import jalview.datamodel.features.FeatureMatcher;
 import jalview.datamodel.features.FeatureMatcherI;
 import jalview.datamodel.features.FeatureMatcherSet;
 import jalview.datamodel.features.FeatureMatcherSetI;
+import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
 import jalview.ext.varna.RnaModel;
 import jalview.gui.AlignFrame;
 import jalview.gui.AlignViewport;
 import jalview.gui.AlignmentPanel;
 import jalview.gui.AppVarna;
 import jalview.gui.ChimeraViewFrame;
+import jalview.gui.ChimeraXViewFrame;
 import jalview.gui.Desktop;
-import jalview.gui.FeatureRenderer;
-import jalview.gui.Jalview2XML_V1;
+import jalview.gui.JalviewChimeraXBindingModel;
 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;
 import jalview.gui.StructureViewerBase;
 import jalview.gui.TreePanel;
+import jalview.io.BackupFiles;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
 import jalview.io.NewickFile;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.ColourSchemeI;
@@ -77,7 +144,9 @@ import jalview.util.StringUtils;
 import jalview.util.jarInputStreamProvider;
 import jalview.util.matcher.Condition;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.PCAModel;
 import jalview.viewmodel.ViewportRanges;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
 import jalview.ws.jws2.Jws2Discoverer;
@@ -92,6 +161,8 @@ import jalview.xml.binding.jalview.Annotation;
 import jalview.xml.binding.jalview.Annotation.ThresholdLine;
 import jalview.xml.binding.jalview.AnnotationColourScheme;
 import jalview.xml.binding.jalview.AnnotationElement;
+import jalview.xml.binding.jalview.DoubleMatrix;
+import jalview.xml.binding.jalview.DoubleVector;
 import jalview.xml.binding.jalview.Feature;
 import jalview.xml.binding.jalview.Feature.OtherData;
 import jalview.xml.binding.jalview.FeatureMatcherSet.CompoundMatcher;
@@ -106,6 +177,11 @@ import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids;
 import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids.StructureState;
 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer;
 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer.SecondaryStructure;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer.Axis;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMax;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMin;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SequencePoint;
 import jalview.xml.binding.jalview.JalviewModel.Tree;
 import jalview.xml.binding.jalview.JalviewModel.UserColours;
 import jalview.xml.binding.jalview.JalviewModel.Viewport;
@@ -118,6 +194,7 @@ import jalview.xml.binding.jalview.MapListType.MapListTo;
 import jalview.xml.binding.jalview.Mapping;
 import jalview.xml.binding.jalview.NoValueColour;
 import jalview.xml.binding.jalview.ObjectFactory;
+import jalview.xml.binding.jalview.PcaDataType;
 import jalview.xml.binding.jalview.Pdbentry.Property;
 import jalview.xml.binding.jalview.Sequence;
 import jalview.xml.binding.jalview.Sequence.DBRef;
@@ -126,54 +203,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.
  * 
@@ -529,24 +558,30 @@ public class Jalview2XML
   public void saveState(File statefile)
   {
     FileOutputStream fos = null;
+
     try
     {
+
       fos = new FileOutputStream(statefile);
+
       JarOutputStream jout = new JarOutputStream(fos);
       saveState(jout);
+      fos.close();
 
     } catch (Exception e)
     {
+      Cache.log.error("Couln't write Jalview state to " + statefile, e);
       // TODO: inform user of the problem - they need to know if their data was
       // not saved !
       if (errorMessage == null)
       {
-        errorMessage = "Couldn't write Jalview Archive to output file '"
+        errorMessage = "Did't write Jalview Archive to output file '"
                 + statefile + "' - See console error log for details";
       }
       else
       {
-        errorMessage += "(output file was '" + statefile + "')";
+        errorMessage += "(Didn't write Jalview Archive to output file '"
+                + statefile + ")";
       }
       e.printStackTrace();
     } finally
@@ -716,7 +751,11 @@ public class Jalview2XML
   {
     try
     {
-      FileOutputStream fos = new FileOutputStream(jarFile);
+      // create backupfiles object and get new temp filename destination
+      BackupFiles backupfiles = new BackupFiles(jarFile);
+      FileOutputStream fos = new FileOutputStream(
+              backupfiles.getTempFilePath());
+
       JarOutputStream jout = new JarOutputStream(fos);
       List<AlignFrame> frames = new ArrayList<>();
 
@@ -738,7 +777,12 @@ public class Jalview2XML
       }
       ;
       jout.close();
-      return true;
+      boolean success = true;
+
+      backupfiles.setWriteSuccess(success);
+      success = backupfiles.rollBackupsAndRenameTempFile();
+
+      return success;
     } catch (Exception ex)
     {
       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
@@ -1048,15 +1092,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());
                 }
               }
             }
@@ -1215,6 +1261,9 @@ public class Jalview2XML
               tree.setXpos(tp.getX());
               tree.setYpos(tp.getY());
               tree.setId(makeHashCode(tp, null));
+              tree.setLinkToAllViews(
+                      tp.getTreeCanvas().isApplyToAllViews());
+
               // jms.addTree(tree);
               object.getTree().add(tree);
             }
@@ -1223,6 +1272,24 @@ public class Jalview2XML
       }
     }
 
+    /*
+     * save PCA viewers
+     */
+    if (!storeDS && Desktop.desktop != null)
+    {
+      for (JInternalFrame frame : Desktop.desktop.getAllFrames())
+      {
+        if (frame instanceof PCAPanel)
+        {
+          PCAPanel panel = (PCAPanel) frame;
+          if (panel.getAlignViewport().getAlignment() == jal)
+          {
+            savePCA(panel, object);
+          }
+        }
+      }
+    }
+
     // SAVE ANNOTATIONS
     /**
      * store forward refs from an annotationRow to any groups
@@ -1441,11 +1508,14 @@ public class Jalview2XML
       view.setFollowHighlight(av.isFollowHighlight());
       view.setFollowSelection(av.followSelection);
       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
+      view.setShowComplementFeatures(av.isShowComplementFeatures());
+      view.setShowComplementFeaturesOnTop(
+              av.isShowComplementFeaturesOnTop());
       if (av.getFeaturesDisplayed() != null)
       {
         FeatureSettings fs = new FeatureSettings();
 
-        FeatureRenderer fr = ap.getSeqPanel().seqCanvas
+        FeatureRendererModel fr = ap.getSeqPanel().seqCanvas
                 .getFeatureRenderer();
         String[] renderOrder = fr.getRenderOrder().toArray(new String[0]);
 
@@ -1630,6 +1700,196 @@ public class Jalview2XML
   }
 
   /**
+   * Writes PCA viewer attributes and computed values to an XML model object and
+   * adds it to the JalviewModel. Any exceptions are reported by logging.
+   */
+  protected void savePCA(PCAPanel panel, JalviewModel object)
+  {
+    try
+    {
+      PcaViewer viewer = new PcaViewer();
+      viewer.setHeight(panel.getHeight());
+      viewer.setWidth(panel.getWidth());
+      viewer.setXpos(panel.getX());
+      viewer.setYpos(panel.getY());
+      viewer.setTitle(panel.getTitle());
+      PCAModel pcaModel = panel.getPcaModel();
+      viewer.setScoreModelName(pcaModel.getScoreModelName());
+      viewer.setXDim(panel.getSelectedDimensionIndex(X));
+      viewer.setYDim(panel.getSelectedDimensionIndex(Y));
+      viewer.setZDim(panel.getSelectedDimensionIndex(Z));
+      viewer.setBgColour(
+              panel.getRotatableCanvas().getBackgroundColour().getRGB());
+      viewer.setScaleFactor(panel.getRotatableCanvas().getScaleFactor());
+      float[] spMin = panel.getRotatableCanvas().getSeqMin();
+      SeqPointMin spmin = new SeqPointMin();
+      spmin.setXPos(spMin[0]);
+      spmin.setYPos(spMin[1]);
+      spmin.setZPos(spMin[2]);
+      viewer.setSeqPointMin(spmin);
+      float[] spMax = panel.getRotatableCanvas().getSeqMax();
+      SeqPointMax spmax = new SeqPointMax();
+      spmax.setXPos(spMax[0]);
+      spmax.setYPos(spMax[1]);
+      spmax.setZPos(spMax[2]);
+      viewer.setSeqPointMax(spmax);
+      viewer.setShowLabels(panel.getRotatableCanvas().isShowLabels());
+      viewer.setLinkToAllViews(
+              panel.getRotatableCanvas().isApplyToAllViews());
+      SimilarityParamsI sp = pcaModel.getSimilarityParameters();
+      viewer.setIncludeGaps(sp.includeGaps());
+      viewer.setMatchGaps(sp.matchGaps());
+      viewer.setIncludeGappedColumns(sp.includeGappedColumns());
+      viewer.setDenominateByShortestLength(sp.denominateByShortestLength());
+
+      /*
+       * sequence points on display
+       */
+      for (jalview.datamodel.SequencePoint spt : pcaModel
+              .getSequencePoints())
+      {
+        SequencePoint point = new SequencePoint();
+        point.setSequenceRef(seqHash(spt.getSequence()));
+        point.setXPos(spt.coord.x);
+        point.setYPos(spt.coord.y);
+        point.setZPos(spt.coord.z);
+        viewer.getSequencePoint().add(point);
+      }
+
+      /*
+       * (end points of) axes on display
+       */
+      for (Point p : panel.getRotatableCanvas().getAxisEndPoints())
+      {
+
+        Axis axis = new Axis();
+        axis.setXPos(p.x);
+        axis.setYPos(p.y);
+        axis.setZPos(p.z);
+        viewer.getAxis().add(axis);
+      }
+
+      /*
+       * raw PCA data (note we are not restoring PCA inputs here -
+       * alignment view, score model, similarity parameters)
+       */
+      PcaDataType data = new PcaDataType();
+      viewer.setPcaData(data);
+      PCA pca = pcaModel.getPcaData();
+
+      DoubleMatrix pm = new DoubleMatrix();
+      saveDoubleMatrix(pca.getPairwiseScores(), pm);
+      data.setPairwiseMatrix(pm);
+
+      DoubleMatrix tm = new DoubleMatrix();
+      saveDoubleMatrix(pca.getTridiagonal(), tm);
+      data.setTridiagonalMatrix(tm);
+
+      DoubleMatrix eigenMatrix = new DoubleMatrix();
+      data.setEigenMatrix(eigenMatrix);
+      saveDoubleMatrix(pca.getEigenmatrix(), eigenMatrix);
+
+      object.getPcaViewer().add(viewer);
+    } catch (Throwable t)
+    {
+      Cache.log.error("Error saving PCA: " + t.getMessage());
+    }
+  }
+
+  /**
+   * Stores values from a matrix into an XML element, including (if present) the
+   * D or E vectors
+   * 
+   * @param m
+   * @param xmlMatrix
+   * @see #loadDoubleMatrix(DoubleMatrix)
+   */
+  protected void saveDoubleMatrix(MatrixI m, DoubleMatrix xmlMatrix)
+  {
+    xmlMatrix.setRows(m.height());
+    xmlMatrix.setColumns(m.width());
+    for (int i = 0; i < m.height(); i++)
+    {
+      DoubleVector row = new DoubleVector();
+      for (int j = 0; j < m.width(); j++)
+      {
+        row.getV().add(m.getValue(i, j));
+      }
+      xmlMatrix.getRow().add(row);
+    }
+    if (m.getD() != null)
+    {
+      DoubleVector dVector = new DoubleVector();
+      for (double d : m.getD())
+      {
+        dVector.getV().add(d);
+      }
+      xmlMatrix.setD(dVector);
+    }
+    if (m.getE() != null)
+    {
+      DoubleVector eVector = new DoubleVector();
+      for (double e : m.getE())
+      {
+        eVector.getV().add(e);
+      }
+      xmlMatrix.setE(eVector);
+    }
+  }
+
+  /**
+   * Loads XML matrix data into a new Matrix object, including the D and/or E
+   * vectors (if present)
+   * 
+   * @param mData
+   * @return
+   * @see Jalview2XML#saveDoubleMatrix(MatrixI, DoubleMatrix)
+   */
+  protected MatrixI loadDoubleMatrix(DoubleMatrix mData)
+  {
+    int rows = mData.getRows();
+    double[][] vals = new double[rows][];
+
+    for (int i = 0; i < rows; i++)
+    {
+      List<Double> dVector = mData.getRow().get(i).getV();
+      vals[i] = new double[dVector.size()];
+      int dvi = 0;
+      for (Double d : dVector)
+      {
+        vals[i][dvi++] = d;
+      }
+    }
+
+    MatrixI m = new Matrix(vals);
+
+    if (mData.getD() != null)
+    {
+      List<Double> dVector = mData.getD().getV();
+      double[] vec = new double[dVector.size()];
+      int dvi = 0;
+      for (Double d : dVector)
+      {
+        vec[dvi++] = d;
+      }
+      m.setD(vec);
+    }
+    if (mData.getE() != null)
+    {
+      List<Double> dVector = mData.getE().getV();
+      double[] vec = new double[dVector.size()];
+      int dvi = 0;
+      for (Double d : dVector)
+      {
+        vec[dvi++] = d;
+      }
+      m.setE(vec);
+    }
+
+    return m;
+  }
+
+  /**
    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
    * for each viewer, with
    * <ul>
@@ -1739,32 +1999,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
-        }
-      }
     }
   }
 
@@ -1791,6 +2042,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
@@ -1857,7 +2126,7 @@ public class Jalview2XML
           final String viewId = viewFrame.getViewId();
           state.setViewId(viewId);
           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
-          state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
+          state.setColourwithAlignPanel(viewFrame.isUsedForColourBy(ap));
           state.setColourByJmol(viewFrame.isColouredByViewer());
           state.setType(viewFrame.getViewerType().toString());
           // pdb.addStructureState(state);
@@ -2257,21 +2526,29 @@ public class Jalview2XML
         parentseq = jds;
       }
     }
+
+    /*
+     * save any dbrefs; special subclass GeneLocus is flagged as 'locus'
+     */
     if (dbrefs != null)
     {
       for (int d = 0; d < dbrefs.length; d++)
       {
         DBRef dbref = new DBRef();
-        dbref.setSource(dbrefs[d].getSource());
-        dbref.setVersion(dbrefs[d].getVersion());
-        dbref.setAccessionId(dbrefs[d].getAccessionId());
-        if (dbrefs[d].hasMap())
+        DBRefEntry dbRefEntry = dbrefs[d];
+        dbref.setSource(dbRefEntry.getSource());
+        dbref.setVersion(dbRefEntry.getVersion());
+        dbref.setAccessionId(dbRefEntry.getAccessionId());
+        if (dbRefEntry instanceof GeneLocus)
+        {
+          dbref.setLocus(true);
+        }
+        if (dbRefEntry.hasMap())
         {
-          Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq,
+          Mapping mp = createVamsasMapping(dbRefEntry.getMap(), parentseq,
                   jds, recurse);
           dbref.setMapping(mp);
         }
-        // vamsasSeq.addDBRef(dbref);
         vamsasSeq.getDBRef().add(dbref);
       }
     }
@@ -2649,16 +2926,7 @@ public class Jalview2XML
       ex.printStackTrace(System.err);
       if (attemptversion1parse)
       {
-        // Is Version 1 Jar file?
-        try
-        {
-          af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
-        } catch (Exception ex2)
-        {
-          System.err.println("Exception whilst loading as jalviewXMLV1:");
-          ex2.printStackTrace();
-          af = null;
-        }
+        // used to attempt to parse as V1 castor-generated xml
       }
       if (Desktop.instance != null)
       {
@@ -2911,53 +3179,42 @@ public class Jalview2XML
    * @param prefix
    *          a prefix for the temporary file name, must be at least three
    *          characters long
-   * @param origFile
+   * @param suffixModel
    *          null or original file - so new file can be given the same suffix
    *          as the old one
    * @return
    */
   protected String copyJarEntry(jarInputStreamProvider jprovider,
-          String jarEntryName, String prefix, String origFile)
+          String jarEntryName, String prefix, String suffixModel)
   {
-    BufferedReader in = null;
-    PrintWriter out = null;
     String suffix = ".tmp";
-    if (origFile == null)
+    if (suffixModel == null)
     {
-      origFile = jarEntryName;
+      suffixModel = jarEntryName;
     }
-    int sfpos = origFile.lastIndexOf(".");
-    if (sfpos > -1 && sfpos < (origFile.length() - 3))
+    int sfpos = suffixModel.lastIndexOf(".");
+    if (sfpos > -1 && sfpos < (suffixModel.length() - 1))
     {
-      suffix = "." + origFile.substring(sfpos + 1);
+      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;
       }
@@ -2968,22 +3225,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;
@@ -3084,8 +3325,10 @@ public class Jalview2XML
                   || tmpSeq.getEnd() != jseq.getEnd())
           {
             System.err.println(
-                    "Warning JAL-2154 regression: updating start/end for sequence "
-                            + tmpSeq.toString() + " to " + jseq);
+                    String.format("Warning JAL-2154 regression: updating start/end for sequence %s from %d/%d to %d/%d",
+                            tmpSeq.getName(), tmpSeq.getStart(),
+                            tmpSeq.getEnd(), jseq.getStart(),
+                            jseq.getEnd()));
           }
         }
         else
@@ -3620,7 +3863,7 @@ public class Jalview2XML
           }
           else
           {
-            cs = ColourSchemeProperty.getColourScheme(al,
+            cs = ColourSchemeProperty.getColourScheme(null, al,
                     jGroup.getColour());
           }
         }
@@ -3799,6 +4042,7 @@ public class Jalview2XML
     if (loadTreesAndStructures)
     {
       loadTrees(jalviewModel, view, af, av, ap);
+      loadPCAViewers(jalviewModel, ap);
       loadPDBStructures(jprovider, jseqs, af, ap);
       loadRnaViewers(jprovider, jseqs, ap);
     }
@@ -3947,8 +4191,8 @@ public class Jalview2XML
           // TODO: verify 'associate with all views' works still
           tp.getTreeCanvas().setViewport(av); // af.viewport;
           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
-
         }
+        tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
         if (tp == null)
         {
           warn("There was a problem recovering stored Newick tree: \n"
@@ -4159,29 +4403,46 @@ 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 type = stateData.getType();
+    if (type == null)
     {
-      createChimeraViewer(viewerData, af, jprovider);
+      type = ViewerType.JMOL.toString();
     }
-    else
+    try
     {
-      /*
-       * else Jmol (if pre-2.9, stateData contains JMOL state string)
-       */
-      createJmolViewer(viewerData, af, jprovider);
+      ViewerType viewerType = ViewerType.valueOf(type);
+      switch (viewerType)
+      {
+      case CHIMERA:
+        createChimeraViewer(viewerData, af, jprovider, false);
+        break;
+      case CHIMERAX:
+        createChimeraViewer(viewerData, af, jprovider, true);
+        break;
+      case PYMOL:
+        createPymolViewer(viewerData, af, jprovider);
+        break;
+      case JMOL:
+        createJmolViewer(viewerData, af, jprovider);
+      }
+    } catch (IllegalArgumentException | NullPointerException e)
+    {
+      Cache.log.error(
+              "Invalid structure viewer type: " + type);
     }
   }
 
   /**
-   * Create a new Chimera viewer.
+   * Create a new Chimera or ChimeraX viewer
    * 
    * @param data
    * @param af
    * @param jprovider
+   * @param isChimeraX
    */
   protected void createChimeraViewer(
           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
-          jarInputStreamProvider jprovider)
+          jarInputStreamProvider jprovider, boolean isChimeraX)
   {
     StructureViewerModel data = viewerData.getValue();
     String chimeraSessionFile = data.getStateData();
@@ -4193,8 +4454,11 @@ public class Jalview2XML
      * 'uniquified' sviewid used to reconstruct the viewer here
      */
     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
+    String extension = isChimeraX
+            ? JalviewChimeraXBindingModel.CHIMERAX_SESSION_EXTENSION
+            : JalviewChimeraBinding.CHIMERA_SESSION_EXTENSION;
     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
-            "chimera", null);
+            "chimera", extension);
 
     Set<Entry<File, StructureData>> fileData = data.getFileData()
             .entrySet();
@@ -4220,9 +4484,13 @@ public class Jalview2XML
             .toArray(new SequenceI[allseqs.size()][]);
     String newViewId = viewerData.getKey();
 
-    ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
-            af.alignPanel, pdbArray, seqsArray, colourByChimera,
-            colourBySequence, newViewId);
+    ChimeraViewFrame cvf = isChimeraX
+            ? new ChimeraXViewFrame(chimeraSessionFile, af.alignPanel,
+                    pdbArray, seqsArray, colourByChimera, colourBySequence,
+                    newViewId)
+            : new ChimeraViewFrame(chimeraSessionFile, af.alignPanel,
+                    pdbArray, seqsArray, colourByChimera, colourBySequence,
+                    newViewId);
     cvf.setSize(data.getWidth(), data.getHeight());
     cvf.setLocation(data.getX(), data.getY());
   }
@@ -4280,7 +4548,7 @@ public class Jalview2XML
           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
           filedat = oldFiles.get(new File(reformatedOldFilename));
         }
-        newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
+        newFileLoc.append(Platform.escapeBackslashes(filedat.getFilePath()));
         pdbfilenames.add(filedat.getFilePath());
         pdbids.add(filedat.getPdbId());
         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
@@ -4731,25 +4999,27 @@ public class Jalview2XML
       }
       else
       {
-        cs = ColourSchemeProperty.getColourScheme(al, view.getBgColour());
+        cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
+                view.getBgColour());
       }
     }
 
+    /*
+     * turn off 'alignment colour applies to all groups'
+     * while restoring global colour scheme
+     */
+    viewport.setColourAppliesToAllGroups(false);
     viewport.setGlobalColourScheme(cs);
     viewport.getResidueShading().setThreshold(pidThreshold,
             view.isIgnoreGapsinConsensus());
     viewport.getResidueShading()
             .setConsensus(viewport.getSequenceConsensusHash());
-    viewport.setColourAppliesToAllGroups(false);
-
     if (safeBoolean(view.isConservationSelected()) && cs != null)
     {
       viewport.getResidueShading()
               .setConservationInc(safeInt(view.getConsThreshold()));
     }
-
     af.changeColour(cs);
-
     viewport.setColourAppliesToAllGroups(true);
 
     viewport
@@ -4767,11 +5037,14 @@ public class Jalview2XML
     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
     viewport.setShowGroupConservation(view.isShowGroupConservation());
+    viewport.setShowComplementFeatures(view.isShowComplementFeatures());
+    viewport.setShowComplementFeaturesOnTop(
+            view.isShowComplementFeaturesOnTop());
 
     // recover feature settings
     if (jm.getFeatureSettings() != null)
     {
-      FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
+      FeatureRendererModel fr = af.alignPanel.getSeqPanel().seqCanvas
               .getFeatureRenderer();
       FeaturesDisplayed fdi;
       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
@@ -4825,7 +5098,8 @@ public class Jalview2XML
           float min = safeFloat(safeFloat(setting.getMin()));
           float max = setting.getMax() == null ? 1f
                   : setting.getMax().floatValue();
-          FeatureColourI gc = new FeatureColour(minColour, maxColour,
+          FeatureColourI gc = new FeatureColour(maxColour, minColour,
+                  maxColour,
                   noValueColour, min, max);
           if (setting.getAttributeName().size() > 0)
           {
@@ -4870,7 +5144,7 @@ public class Jalview2XML
         }
         else
         {
-          featureOrder.put(featureType, new Float(
+          featureOrder.put(featureType, Float.valueOf(
                   fs / jm.getFeatureSettings().getSetting().size()));
         }
         if (safeBoolean(setting.isDisplay()))
@@ -4882,7 +5156,7 @@ public class Jalview2XML
       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
       {
         Group grp = jm.getFeatureSettings().getGroup().get(gs);
-        fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
+        fgtable.put(grp.getName(), Boolean.valueOf(grp.isDisplay()));
       }
       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
@@ -5027,7 +5301,7 @@ public class Jalview2XML
     else
     {
       cs = new AnnotationColourGradient(matchedAnnotation,
-              ColourSchemeProperty.getColourScheme(al,
+              ColourSchemeProperty.getColourScheme(af.getViewport(), al,
                       viewAnnColour.getColourScheme()),
               safeInt(viewAnnColour.getAboveThreshold()));
     }
@@ -5220,21 +5494,47 @@ public class Jalview2XML
   {
     jalview.datamodel.AlignmentI ds = getDatasetFor(
             vamsasSet.getDatasetId());
+    AlignmentI xtant_ds = ds;
+    if (xtant_ds == null)
+    {
+      // good chance we are about to create a new dataset, but check if we've
+      // seen some of the dataset sequence IDs before.
+      // TODO: skip this check if we are working with project generated by
+      // version 2.11 or later
+      xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
+      if (xtant_ds != null)
+      {
+        ds = xtant_ds;
+        addDatasetRef(vamsasSet.getDatasetId(), ds);
+      }
+    }
     Vector dseqs = null;
-    if (ds == null)
+    if (!ignoreUnrefed)
     {
-      if (!ignoreUnrefed)
+      // recovering an alignment View
+      AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
+      if (seqSetDS != null)
       {
-        // try to resolve the dataset via uniqueSeqSetId
-        ds = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
-        if (ds != null)
+        if (ds != null && ds != seqSetDS)
         {
-          addDatasetRef(vamsasSet.getDatasetId(), ds);
+          warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
+                  + " - CDS/Protein crossreference data may be lost");
+          if (xtant_ds != null)
+          {
+            // This can only happen if the unique sequence set ID was bound to a
+            // dataset that did not contain any of the sequences in the view
+            // currently being restored.
+            warn("JAL-3171 SERIOUS!  TOTAL CONFUSION - please consider contacting the Jalview Development team so they can investigate why your project caused this message to be displayed.");
+          }
         }
+        ds = seqSetDS;
+        addDatasetRef(vamsasSet.getDatasetId(), ds);
       }
     }
     if (ds == null)
     {
+      // try even harder to restore dataset
+      AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
       // create a list of new dataset sequences
       dseqs = new Vector();
     }
@@ -5260,9 +5560,55 @@ public class Jalview2XML
       // register dataset for the alignment's uniqueSeqSetId for legacy projects
       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
     }
+    updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
   }
 
   /**
+   * XML dataset sequence ID to materialised dataset reference
+   */
+  HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
+
+  /**
+   * @return the first materialised dataset reference containing a dataset
+   *         sequence referenced in the given view
+   * @param list
+   *          - sequences from the view
+   */
+  AlignmentI checkIfHasDataset(List<Sequence> list)
+  {
+    for (Sequence restoredSeq : list)
+    {
+      AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
+      if (datasetFor != null)
+      {
+        return datasetFor;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Register ds as the containing dataset for the dataset sequences referenced
+   * by sequences in list
+   * 
+   * @param list
+   *          - sequences in a view
+   * @param ds
+   */
+  void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
+  {
+    for (Sequence restoredSeq : list)
+    {
+      AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
+      if (prevDS != null && prevDS != ds)
+      {
+        warn("Dataset sequence appears in many datasets: "
+                + restoredSeq.getDsseqid());
+        // TODO: try to merge!
+      }
+    }
+  }
+  /**
    * 
    * @param vamsasSeq
    *          sequence definition to create/merge dataset sequence for
@@ -5483,13 +5829,29 @@ public class Jalview2XML
     return datasetId;
   }
 
+  /**
+   * Add any saved DBRefEntry's to the sequence. An entry flagged as 'locus' is
+   * constructed as a special subclass GeneLocus.
+   * 
+   * @param datasetSequence
+   * @param sequence
+   */
   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
   {
     for (int d = 0; d < sequence.getDBRef().size(); d++)
     {
       DBRef dr = sequence.getDBRef().get(d);
-      jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
-              dr.getSource(), dr.getVersion(), dr.getAccessionId());
+      DBRefEntry entry;
+      if (dr.isLocus())
+      {
+        entry = new GeneLocus(dr.getSource(), dr.getVersion(),
+                dr.getAccessionId());
+      }
+      else
+      {
+        entry = new DBRefEntry(dr.getSource(), dr.getVersion(),
+                dr.getAccessionId());
+      }
       if (dr.getMapping() != null)
       {
         entry.setMap(addMapping(dr.getMapping()));
@@ -5521,15 +5883,16 @@ public class Jalview2XML
     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
             fto, m.getMapFromUnit().intValue(),
             m.getMapToUnit().intValue());
-    // if (m.getMappingChoice() != null)
-    // {
-    // MappingChoice mc = m.getMappingChoice();
+
+    /*
+     * (optional) choice of dseqFor or Sequence
+     */
     if (m.getDseqFor() != null)
     {
       String dsfor = m.getDseqFor();
       if (seqRefIds.containsKey(dsfor))
       {
-        /**
+        /*
          * recover from hash
          */
         jmap.setTo(seqRefIds.get(dsfor));
@@ -5539,9 +5902,9 @@ public class Jalview2XML
         frefedSequence.add(newMappingRef(dsfor, jmap));
       }
     }
-    else
+    else if (m.getSequence() != null)
     {
-      /**
+      /*
        * local sequence definition
        */
       Sequence ms = m.getSequence();
@@ -5863,6 +6226,183 @@ public class Jalview2XML
   }
 
   /**
+   * Loads any saved PCA viewers
+   * 
+   * @param jms
+   * @param ap
+   */
+  protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
+  {
+    try
+    {
+      List<PcaViewer> pcaviewers = model.getPcaViewer();
+      for (PcaViewer viewer : pcaviewers)
+      {
+        String modelName = viewer.getScoreModelName();
+        SimilarityParamsI params = new SimilarityParams(
+                viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
+                viewer.isIncludeGaps(),
+                viewer.isDenominateByShortestLength());
+
+        /*
+         * create the panel (without computing the PCA)
+         */
+        PCAPanel panel = new PCAPanel(ap, modelName, params);
+
+        panel.setTitle(viewer.getTitle());
+        panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
+                viewer.getWidth(), viewer.getHeight()));
+
+        boolean showLabels = viewer.isShowLabels();
+        panel.setShowLabels(showLabels);
+        panel.getRotatableCanvas().setShowLabels(showLabels);
+        panel.getRotatableCanvas()
+                .setBgColour(new Color(viewer.getBgColour()));
+        panel.getRotatableCanvas()
+                .setApplyToAllViews(viewer.isLinkToAllViews());
+
+        /*
+         * load PCA output data
+         */
+        ScoreModelI scoreModel = ScoreModels.getInstance()
+                .getScoreModel(modelName, ap);
+        PCA pca = new PCA(null, scoreModel, params);
+        PcaDataType pcaData = viewer.getPcaData();
+
+        MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
+        pca.setPairwiseScores(pairwise);
+
+        MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
+        pca.setTridiagonal(triDiag);
+
+        MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
+        pca.setEigenmatrix(result);
+
+        panel.getPcaModel().setPCA(pca);
+
+        /*
+         * we haven't saved the input data! (JAL-2647 to do)
+         */
+        panel.setInputData(null);
+
+        /*
+         * add the sequence points for the PCA display
+         */
+        List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
+        for (SequencePoint sp : viewer.getSequencePoint())
+        {
+          String seqId = sp.getSequenceRef();
+          SequenceI seq = seqRefIds.get(seqId);
+          if (seq == null)
+          {
+            throw new IllegalStateException(
+                    "Unmatched seqref for PCA: " + seqId);
+          }
+          Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
+          jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
+                  seq, pt);
+          seqPoints.add(seqPoint);
+        }
+        panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
+
+        /*
+         * set min-max ranges and scale after setPoints (which recomputes them)
+         */
+        panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
+        SeqPointMin spMin = viewer.getSeqPointMin();
+        float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
+            spMin.getZPos() };
+        SeqPointMax spMax = viewer.getSeqPointMax();
+        float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
+            spMax.getZPos() };
+        panel.getRotatableCanvas().setSeqMinMax(min, max);
+
+        // todo: hold points list in PCAModel only
+        panel.getPcaModel().setSequencePoints(seqPoints);
+
+        panel.setSelectedDimensionIndex(viewer.getXDim(), X);
+        panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
+        panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
+
+        // is this duplication needed?
+        panel.setTop(seqPoints.size() - 1);
+        panel.getPcaModel().setTop(seqPoints.size() - 1);
+
+        /*
+         * add the axes' end points for the display
+         */
+        for (int i = 0; i < 3; i++)
+        {
+          Axis axis = viewer.getAxis().get(i);
+          panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
+                  axis.getXPos(), axis.getYPos(), axis.getZPos());
+        }
+
+        Desktop.addInternalFrame(panel, MessageManager.formatMessage(
+                "label.calc_title", "PCA", modelName), 475, 450);
+      }
+    } catch (Exception ex)
+    {
+      Cache.log.error("Error loading PCA: " + ex.toString());
+    }
+  }
+
+  /**
+   * 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
@@ -6125,7 +6665,7 @@ public class Jalview2XML
         noValueColour = maxcol;
       }
   
-      colour = new FeatureColour(mincol, maxcol, noValueColour,
+      colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
               safeFloat(colourModel.getMin()),
               safeFloat(colourModel.getMax()));
       final List<String> attributeName = colourModel.getAttributeName();