Merge branch 'develop' into improvement/JAL-4124_dont_duplacate_PAE_data_acrossviews
authorJames Procter <j.procter@dundee.ac.uk>
Mon, 23 Oct 2023 11:14:13 +0000 (12:14 +0100)
committerJames Procter <j.procter@dundee.ac.uk>
Mon, 23 Oct 2023 11:14:13 +0000 (12:14 +0100)
1  2 
src/jalview/datamodel/Alignment.java
src/jalview/project/Jalview2XML.java
src/jalview/renderer/ContactMapRenderer.java
test/jalview/project/Jalview2xmlTests.java

@@@ -2057,7 -2057,7 +2057,7 @@@ public class Alignment implements Align
      if (cm == null && _aa.sequenceRef != null)
      {
        cm = _aa.sequenceRef.getContactMatrixFor(_aa);
 -      if (cm == null)
 +      if (cm == null && _aa.sequenceRef.getDatasetSequence()!=null)
        {
          // TODO fix up this logic and unify with getContactListFor
          cm = _aa.sequenceRef.getDatasetSequence().getContactMatrixFor(_aa);
    @Override
    public ContactListI getContactListFor(AlignmentAnnotation _aa, int column)
    {
+     if (_aa.annotations==null || column>=_aa.annotations.length || column<0)
+     {
+       return null;
+     }
      ContactListI cl = cmholder.getContactListFor(_aa, column);
      if (cl == null && _aa.groupRef != null)
      {
@@@ -26,7 -26,6 +26,6 @@@ import static jalview.math.RotatableMat
  
  import java.awt.Color;
  import java.awt.Font;
- import java.awt.FontMetrics;
  import java.awt.Rectangle;
  import java.io.BufferedReader;
  import java.io.ByteArrayInputStream;
@@@ -87,19 -86,17 +86,20 @@@ import jalview.api.analysis.SimilarityP
  import jalview.api.structures.JalviewStructureDisplayI;
  import jalview.bin.Cache;
  import jalview.bin.Console;
+ import jalview.bin.Jalview;
  import jalview.datamodel.AlignedCodonFrame;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
 +import jalview.datamodel.ContactListI;
  import jalview.datamodel.ContactMatrix;
  import jalview.datamodel.ContactMatrixI;
  import jalview.datamodel.DBRefEntry;
 +import jalview.datamodel.FloatContactMatrix;
  import jalview.datamodel.GeneLocus;
  import jalview.datamodel.GraphLine;
  import jalview.datamodel.GroupSet;
 +import jalview.datamodel.GroupSetI;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.Point;
  import jalview.datamodel.RnaViewerModel;
@@@ -202,7 -199,6 +202,7 @@@ import jalview.xml.binding.jalview.Jalv
  import jalview.xml.binding.jalview.MapListType;
  import jalview.xml.binding.jalview.MapListType.MapListFrom;
  import jalview.xml.binding.jalview.MapListType.MapListTo;
 +import jalview.xml.binding.jalview.MapOnAMatrixType;
  import jalview.xml.binding.jalview.Mapping;
  import jalview.xml.binding.jalview.MatrixType;
  import jalview.xml.binding.jalview.NoValueColour;
@@@ -272,7 -268,7 +272,7 @@@ public class Jalview2XM
  
    Map<String, SequenceI> incompleteSeqs = null;
  
 -  List<SeqFref> frefedSequence = null;
 +  List<forwardRef> frefedSequence = null;
  
    boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
  
     * entry names
     */
    private Map<RnaModel, String> rnaSessions = new HashMap<>();
 +  
 +  /**
 +   * map from contact matrices to their XML ids
 +   */
 +  private Map<ContactMatrixI,String> contactMatrices = new HashMap<>();
 +  private Map<String, ContactMatrixI> contactMatrixRefs = new HashMap<>();
 +  private List<jalview.xml.binding.jalview.MatrixType> xmlMatrices= new ArrayList<>();
  
    /**
     * A helper method for safely using the value of an optional attribute that
    }
  
    /**
 -   * base class for resolving forward references to sequences by their ID
 +   * base class for resolving forward references to an as-yet unmarshalled object referenced by already unmarshalled objects
     * 
     * @author jprocter
     *
     */
 -  abstract class SeqFref
 -  {
 +  abstract class forwardRef {
      String sref;
  
      String type;
  
 -    public SeqFref(String _sref, String type)
 +    public forwardRef(String _sref, String type)
      {
        sref = _sref;
        this.type = type;
        return sref;
      }
  
 +    public abstract boolean isResolvable();
 +    /**
 +     * @return true if the forward reference was fully resolved
 +     */
 +    abstract boolean resolve();
 +
 +    @Override
 +    public String toString()
 +    {
 +      return type + " reference to " + sref;
 +    }
 +  }
 +  /**
 +   * resolve forward references to sequences by their ID
 +   * @author jprocter
 +   */
 +  abstract class SeqFref extends forwardRef
 +  {
 +    public SeqFref(String _sref, String type)
 +    {
 +      super(_sref, type);
 +    }
      public SequenceI getSrefSeq()
      {
        return seqRefIds.get(sref);
        }
        return sq;
      }
 -
 -    /**
 -     * @return true if the forward reference was fully resolved
 -     */
 -    abstract boolean resolve();
 -
 -    @Override
 -    public String toString()
 -    {
 -      return type + " reference to " + sref;
 -    }
    }
  
    /**
      };
      return fref;
    }
 +  
 +  public forwardRef newMatrixFref(final String matRef,
 +          final jalview.util.MapList mapping, final AlignmentAnnotation jaa)
 +  {
 +    forwardRef fref = new forwardRef(matRef,
 +            "Matrix Reference for sequence and annotation")
 +    {
 +
 +      @Override
 +      boolean resolve()
 +      {
 +        ContactMatrixI cm = contactMatrixRefs.get(matRef);
 +        PAEContactMatrix newpae = new PAEContactMatrix(jaa.sequenceRef,
 +                mapping, cm);
 +
 +        jaa.sequenceRef.addContactListFor(jaa, newpae);
 +        return true;
 +      }
 +
 +      @Override
 +      public boolean isResolvable()
 +      {
 +        return (contactMatrixRefs.get(matRef) != null);
 +      }
 +    };
 +    return fref;
 +  }
  
    public void resolveFrefedSequences()
    {
 -    Iterator<SeqFref> nextFref = frefedSequence.iterator();
 +    Iterator<forwardRef> nextFref = frefedSequence.iterator();
      int toresolve = frefedSequence.size();
      int unresolved = 0, failedtoresolve = 0;
      while (nextFref.hasNext())
      {
 -      SeqFref ref = nextFref.next();
 +      forwardRef ref = nextFref.next();
        if (ref.isResolvable())
        {
          try
            }
          } catch (Exception x)
          {
-           System.err.println(
+           jalview.bin.Console.errPrintln(
                    "IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
                            + ref.getSref());
            x.printStackTrace();
      }
      if (unresolved > 0)
      {
-       System.err.println("Jalview Project Import: There were " + unresolved
+       jalview.bin.Console.errPrintln("Jalview Project Import: There were "
+               + unresolved
                + " forward references left unresolved on the stack.");
      }
      if (failedtoresolve > 0)
      {
-       System.err.println("SERIOUS! " + failedtoresolve
+       jalview.bin.Console.errPrintln("SERIOUS! " + failedtoresolve
                + " resolvable forward references failed to resolve.");
      }
      if (incompleteSeqs != null && incompleteSeqs.size() > 0)
      {
-       System.err.println(
+       jalview.bin.Console.errPrintln(
                "Jalview Project Import: There are " + incompleteSeqs.size()
                        + " sequences which may have incomplete metadata.");
        if (incompleteSeqs.size() < 10)
        {
          for (SequenceI s : incompleteSeqs.values())
          {
-           System.err.println(s.toString());
+           jalview.bin.Console.errPrintln(s.toString());
          }
        }
        else
        {
-         System.err.println(
+         jalview.bin.Console.errPrintln(
                  "Too many to report. Skipping output of incomplete sequences.");
        }
      }
     */
    public void saveState(JarOutputStream jout)
    {
-     AlignFrame[] frames = Desktop.getAlignFrames();
+     AlignFrame[] frames = Desktop.getDesktopAlignFrames();
  
      setStateSavedUpToDate(true);
  
        object.setCreationDate(now);
      } catch (DatatypeConfigurationException e)
      {
-       System.err.println("error writing date: " + e.toString());
+       jalview.bin.Console.errPrintln("error writing date: " + e.toString());
      }
      object.setVersion(Cache.getDefault("VERSION", "Development Build"));
  
            // HAPPEN! (PF00072.15.stk does this)
            // JBPNote: Uncomment to debug writing out of files that do not read
            // back in due to ArrayOutOfBoundExceptions.
-           // System.err.println("vamsasSeq backref: "+id+"");
-           // System.err.println(jds.getName()+"
+           // jalview.bin.Console.errPrintln("vamsasSeq backref: "+id+"");
+           // jalview.bin.Console.errPrintln(jds.getName()+"
            // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
-           // System.err.println("Hashcode: "+seqHash(jds));
+           // jalview.bin.Console.errPrintln("Hashcode: "+seqHash(jds));
            // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
-           // System.err.println(rsq.getName()+"
+           // jalview.bin.Console.errPrintln(rsq.getName()+"
            // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
-           // System.err.println("Hashcode: "+seqHash(rsq));
+           // jalview.bin.Console.errPrintln("Hashcode: "+seqHash(rsq));
          }
          else
          {
             * only view *should* be coped with sensibly.
             */
            // This must have been loaded, is it still visible?
-           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
-           String matchedFile = null;
-           for (int f = frames.length - 1; f > -1; f--)
+           List<JalviewStructureDisplayI> viewFrames = new ArrayList<>();
+           if (Desktop.desktop != null)
            {
-             if (frames[f] instanceof StructureViewerBase)
+             JInternalFrame[] jifs = Desktop.desktop.getAllFrames();
+             if (jifs != null)
              {
-               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
-               matchedFile = saveStructureViewer(ap, jds, pdb, entry,
-                       viewIds, matchedFile, viewFrame);
-               /*
-                * Only store each structure viewer's state once in the project
-                * jar. First time through only (storeDS==false)
-                */
-               String viewId = viewFrame.getViewId();
-               String viewerType = viewFrame.getViewerType().toString();
-               if (!storeDS && !viewIds.contains(viewId))
+               for (JInternalFrame jif : jifs)
                {
-                 viewIds.add(viewId);
-                 File viewerState = viewFrame.saveSession();
-                 if (viewerState != null)
-                 {
-                   copyFileToJar(jout, viewerState.getPath(),
-                           getViewerJarEntryName(viewId), viewerType);
-                 }
-                 else
+                 if (jif instanceof JalviewStructureDisplayI)
                  {
-                   Console.error(
-                           "Failed to save viewer state for " + viewerType);
+                   viewFrames.add((JalviewStructureDisplayI) jif);
                  }
                }
              }
            }
+           else if (Jalview.isHeadlessMode()
+                   && Jalview.getInstance().getCommands() != null)
+           {
+             viewFrames.addAll(
+                     StructureViewerBase.getAllStructureViewerBases());
+           }
+           String matchedFile = null;
+           for (JalviewStructureDisplayI viewFrame : viewFrames)
+           {
+             matchedFile = saveStructureViewer(ap, jds, pdb, entry, viewIds,
+                     matchedFile, viewFrame);
+             /*
+              * Only store each structure viewer's state once in the project
+              * jar. First time through only (storeDS==false)
+              */
+             String viewId = viewFrame.getViewId();
+             String viewerType = viewFrame.getViewerType().toString();
+             if (!storeDS && !viewIds.contains(viewId))
+             {
+               viewIds.add(viewId);
+               File viewerState = viewFrame.saveSession();
+               if (viewerState != null)
+               {
+                 copyFileToJar(jout, viewerState.getPath(),
+                         getViewerJarEntryName(viewId), viewerType);
+               }
+               else
+               {
+                 Console.error(
+                         "Failed to save viewer state for " + viewerType);
+               }
+             }
+           }
  
            if (matchedFile != null || entry.getFile() != null)
            {
        // jms.addViewport(view);
        object.getViewport().add(view);
      }
 +    
 +    
 +    if (storeDS)
 +    {
 +      // store matrices referenced by any views or annotation in this dataset
 +      if (xmlMatrices!=null && xmlMatrices.size()>0)
 +      {
 +        Console.debug("Adding "+xmlMatrices.size()+" matrices to dataset.");
 +        vamsasSet.getMatrix().addAll(xmlMatrices);
 +        xmlMatrices.clear();
 +      }
 +    }
 +
 +    
      // object.setJalviewModelSequence(jms);
      // object.getVamsasModel().addSequenceSet(vamsasSet);
      object.getVamsasModel().getSequenceSet().add(vamsasSet);
        try
        {
          fileName = fileName.replace('\\', '/');
-         System.out.println("Writing jar entry " + fileName);
+         jalview.bin.Console.outPrintln("Writing jar entry " + fileName);
          JarEntry entry = new JarEntry(fileName);
          jout.putNextEntry(entry);
          PrintWriter pout = new PrintWriter(
        } catch (Exception ex)
        {
          // TODO: raise error in GUI if marshalling failed.
-         System.err.println("Error writing Jalview project");
+         jalview.bin.Console.errPrintln("Error writing Jalview project");
          ex.printStackTrace();
        }
      }
        File file = new File(infilePath);
        if (file.exists() && jout != null)
        {
-         System.out.println(
+         jalview.bin.Console.outPrintln(
                  "Writing jar entry " + jarEntryName + " (" + msg + ")");
          jout.putNextEntry(new JarEntry(jarEntryName));
          copyAll(is, jout);
     */
    protected String saveStructureViewer(AlignmentPanel ap, SequenceI jds,
            Pdbids pdb, PDBEntry entry, List<String> viewIds,
-           String matchedFile, StructureViewerBase viewFrame)
+           String matchedFile, JalviewStructureDisplayI viewFrame)
    {
      final AAStructureBindingModel bindingModel = viewFrame.getBinding();
  
          {
            StructureState state = new StructureState();
            state.setVisible(true);
-           state.setXpos(viewFrame.getX());
+           state.setXpos(viewFrame.getY());
            state.setYpos(viewFrame.getY());
            state.setWidth(viewFrame.getWidth());
            state.setHeight(viewFrame.getHeight());
                      .getContactMatrixFor(annotation);
              if (cm != null)
              {
 -              MatrixType xmlmat = new MatrixType();
 -              xmlmat.setType(cm.getType());
 -              xmlmat.setRows(BigInteger.valueOf(cm.getWidth()));
 -              xmlmat.setCols(BigInteger.valueOf(cm.getHeight()));
 -              // consider using an opaque to/from -> allow instance to control
 -              // its representation ?
 -              xmlmat.setElements(ContactMatrix.contactToFloatString(cm));
 -              if (cm.hasGroups())
 -              {
 -                for (BitSet gp : cm.getGroups())
 -                {
 -                  xmlmat.getGroups().add(stringifyBitset(gp));
 -                }
 -              }
 -              if (cm.hasTree())
 -              {
 -                // provenance object for tree ?
 -                xmlmat.getNewick().add(cm.getNewick());
 -                xmlmat.setTreeMethod(cm.getTreeMethod());
 -              }
 -              if (cm.hasCutHeight())
 -              {
 -                xmlmat.setCutHeight(cm.getCutHeight());
 -              }
 -              // set/get properties
 -              if (cm instanceof MappableContactMatrixI)
 -              {
 -                jalview.util.MapList mlst = ((MappableContactMatrixI) cm)
 -                        .getMapFor(annotation.sequenceRef);
 -                if (mlst != null)
 -                {
 -                  MapListType mp = new MapListType();
 -                  List<int[]> r = mlst.getFromRanges();
 -                  for (int[] range : r)
 -                  {
 -                    MapListFrom mfrom = new MapListFrom();
 -                    mfrom.setStart(range[0]);
 -                    mfrom.setEnd(range[1]);
 -                    // mp.addMapListFrom(mfrom);
 -                    mp.getMapListFrom().add(mfrom);
 -                  }
 -                  r = mlst.getToRanges();
 -                  for (int[] range : r)
 -                  {
 -                    MapListTo mto = new MapListTo();
 -                    mto.setStart(range[0]);
 -                    mto.setEnd(range[1]);
 -                    // mp.addMapListTo(mto);
 -                    mp.getMapListTo().add(mto);
 -                  }
 -                  mp.setMapFromUnit(
 -                          BigInteger.valueOf(mlst.getFromRatio()));
 -                  mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
 -                  xmlmat.setMapping(mp);
 -                }
 -              }
 -              // and add to model
 -              an.getContactmatrix().add(xmlmat);
 +              storeMatrixFor(vamsasSet, an,annotation, cm);
              }
            }
          }
  
    }
  
 +  private void storeMatrixFor(SequenceSet root, Annotation an, AlignmentAnnotation annotation, ContactMatrixI cm)
 +  {
 +    String cmId = contactMatrices.get(cm);
 +    MatrixType xmlmat=null;
 +    
 +    // first create an xml ref for the matrix data, if none exist
 +    if (cmId == null)
 +    {
 +      xmlmat = new MatrixType();
 +      xmlmat.setType(cm.getType());
 +      xmlmat.setRows(BigInteger.valueOf(cm.getWidth()));
 +      xmlmat.setCols(BigInteger.valueOf(cm.getHeight()));
 +      // consider using an opaque to/from -> allow instance to control
 +      // its representation ?
 +      xmlmat.setElements(ContactMatrix.contactToFloatString(cm));
 +      if (cm.hasGroups())
 +      {
 +        for (BitSet gp : cm.getGroups())
 +        {
 +          xmlmat.getGroups().add(stringifyBitset(gp));
 +        }
 +      }
 +      if (cm.hasTree())
 +      {
 +        // provenance object for tree ?
 +        xmlmat.getNewick().add(cm.getNewick());
 +        xmlmat.setTreeMethod(cm.getTreeMethod());
 +      }
 +      if (cm.hasCutHeight())
 +      {
 +        xmlmat.setCutHeight(cm.getCutHeight());
 +      }
 +      xmlmat.setId(cmId = "m"+contactMatrices.size()+System.currentTimeMillis());
 +      Console.trace("Matrix data stored :"+cmId);
 +      contactMatrices.put(cm, cmId);
 +      contactMatrixRefs.put(cmId, cm);
 +      xmlMatrices.add(xmlmat);
 +    } else {
 +      Console.trace("Existing Matrix stored :"+cmId);
 +    }
 +
 +    // now store mapping
 +
 +    MapOnAMatrixType xmlmatmapping = new MapOnAMatrixType();
 +    xmlmatmapping.setMatrix(cmId);
 +    
 +    // Pretty much all matrices currently managed in this way are
 +    // mappableContactMatrixI implementations - but check anyway
 +    if (cm instanceof MappableContactMatrixI)
 +    {
 +      jalview.util.MapList mlst = ((MappableContactMatrixI) cm)
 +              .getMapFor(annotation.sequenceRef);
 +      if (mlst != null)
 +      {
 +        MapListType mp = new MapListType();
 +        List<int[]> r = mlst.getFromRanges();
 +        for (int[] range : r)
 +        {
 +          MapListFrom mfrom = new MapListFrom();
 +          mfrom.setStart(range[0]);
 +          mfrom.setEnd(range[1]);
 +          // mp.addMapListFrom(mfrom);
 +          mp.getMapListFrom().add(mfrom);
 +        }
 +        r = mlst.getToRanges();
 +        for (int[] range : r)
 +        {
 +          MapListTo mto = new MapListTo();
 +          mto.setStart(range[0]);
 +          mto.setEnd(range[1]);
 +          // mp.addMapListTo(mto);
 +          mp.getMapListTo().add(mto);
 +        }
 +        mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
 +        mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
 +        xmlmatmapping.setMapping(mp);
 +      }
 +    }
 +    // and add to model
 +    an.getContactmatrix().add(xmlmatmapping);
 +  }
 +
    private String stringifyBitset(BitSet gp)
    {
      StringBuilder sb = new StringBuilder();
      return BitSet.valueOf(newlongvals);
  
    }
    private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
    {
      AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
          });
        } catch (Exception x)
        {
-         System.err.println("Error loading alignment: " + x.getMessage());
+         jalview.bin.Console
+                 .errPrintln("Error loading alignment: " + x.getMessage());
        }
      }
      return af;
          {
            if (bytes != null)
            {
-             // System.out.println("Jalview2XML: opening byte jarInputStream for
+             // jalview.bin.Console.outPrintln("Jalview2XML: opening byte
+             // jarInputStream for
              // bytes.length=" + bytes.length);
              return new JarInputStream(new ByteArrayInputStream(bytes));
            }
            if (_url != null)
            {
-             // System.out.println("Jalview2XML: opening url jarInputStream for "
+             // jalview.bin.Console.outPrintln("Jalview2XML: opening url
+             // jarInputStream for "
              // + _url);
              return new JarInputStream(_url.openStream());
            }
            else
            {
-             // System.out.println("Jalview2XML: opening file jarInputStream for
+             // jalview.bin.Console.outPrintln("Jalview2XML: opening file
+             // jarInputStream for
              // " + file);
              return new JarInputStream(new FileInputStream(file));
            }
        initSeqRefs();
      }
      AlignFrame af = null, _af = null;
 +    List<AlignFrame> toRepaint=new ArrayList<AlignFrame>();
      IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
      Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
      final String file = jprovider.getFilename();
              if (_af != null && object.getViewport().size() > 0)
              // getJalviewModelSequence().getViewportCount() > 0)
              {
 +              toRepaint.add(_af);
                if (af == null)
                {
                  // store a reference to the first view
        } while (jarentry != null);
        jin.close();
        resolveFrefedSequences();
 +      for (AlignFrame alignFrame:toRepaint)
 +      {
 +        alignFrame.repaint();
 +      }
      } catch (IOException ex)
      {
        ex.printStackTrace();
        errorMessage = "Couldn't locate Jalview XML file : " + file;
-       System.err.println(
+       jalview.bin.Console.errPrintln(
                "Exception whilst loading jalview XML file : " + ex + "\n");
      } catch (Exception ex)
      {
-       System.err.println("Parsing as Jalview Version 2 file failed.");
+       jalview.bin.Console
+               .errPrintln("Parsing as Jalview Version 2 file failed.");
        ex.printStackTrace(System.err);
        if (attemptversion1parse)
        {
        }
        if (af != null)
        {
-         System.out.println("Successfully loaded archive file");
+         jalview.bin.Console.outPrintln("Successfully loaded archive file");
          return af;
        }
        ex.printStackTrace();
  
-       System.err.println(
+       jalview.bin.Console.errPrintln(
                "Exception whilst loading jalview XML file : " + ex + "\n");
      } catch (OutOfMemoryError e)
      {
        // Don't use the OOM Window here
        errorMessage = "Out of memory loading jalview XML file";
-       System.err.println("Out of memory whilst loading jalview XML file");
+       jalview.bin.Console
+               .errPrintln("Out of memory whilst loading jalview XML file");
        e.printStackTrace();
      }
  
          Desktop.addInternalFrame(af, view.getTitle(),
                  safeInt(view.getWidth()), safeInt(view.getHeight()));
          af.setMenusForViewport();
-         System.err.println("Failed to restore view " + view.getTitle()
-                 + " to split frame");
+         jalview.bin.Console.errPrintln("Failed to restore view "
+                 + view.getTitle() + " to split frame");
        }
      }
  
        }
        else
        {
-         System.err.println("Problem loading Jalview file: " + errorMessage);
+         jalview.bin.Console.errPrintln(
+                 "Problem loading Jalview file: " + errorMessage);
        }
      }
      errorMessage = null;
      }
  
      // ////////////////////////////////
 +    // LOAD MATRICES (IF ANY)
 +    
 +    if (vamsasSet.getMatrix()!=null && vamsasSet.getMatrix().size()>0)
 +    {
 +      importMatrixData(vamsasSet.getMatrix());
 +    }
 +    
 +    // ////////////////////////////////
      // LOAD SEQUENCES
  
      List<SequenceI> hiddenSeqs = null;
            if (tmpSeq.getStart() != jseq.getStart()
                    || tmpSeq.getEnd() != jseq.getEnd())
            {
-             System.err.println(String.format(
+             jalview.bin.Console.errPrintln(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()));
            if (annotation.getContactmatrix() != null
                    && annotation.getContactmatrix().size() > 0)
            {
 -            for (MatrixType xmlmat : annotation.getContactmatrix())
 +            for (MapOnAMatrixType xmlmat : annotation.getContactmatrix())
              {
 -              if (PAEContactMatrix.PAEMATRIX.equals(xmlmat.getType()))
 -              {
 -                if (!xmlmat.getRows().equals(xmlmat.getCols()))
 -                {
 -                  Console.error("Can't handle non square PAE Matrices");
 -                }
 -                else
 -                {
 -                  float[][] elements = ContactMatrix
 -                          .fromFloatStringToContacts(xmlmat.getElements(),
 -                                  xmlmat.getCols().intValue(),
 -                                  xmlmat.getRows().intValue());
 -                  jalview.util.MapList mapping = null;
 -                  if (xmlmat.getMapping() != null)
 -                  {
 -                    MapListType m = xmlmat.getMapping();
 -                    // Mapping m = dr.getMapping();
 -                    int fr[] = new int[m.getMapListFrom().size() * 2];
 -                    Iterator<MapListFrom> from = m.getMapListFrom()
 -                            .iterator();// enumerateMapListFrom();
 -                    for (int _i = 0; from.hasNext(); _i += 2)
 -                    {
 -                      MapListFrom mf = from.next();
 -                      fr[_i] = mf.getStart();
 -                      fr[_i + 1] = mf.getEnd();
 -                    }
 -                    int fto[] = new int[m.getMapListTo().size() * 2];
 -                    Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
 -                    for (int _i = 0; to.hasNext(); _i += 2)
 -                    {
 -                      MapListTo mf = to.next();
 -                      fto[_i] = mf.getStart();
 -                      fto[_i + 1] = mf.getEnd();
 -                    }
 -
 -                    mapping = new jalview.util.MapList(fr, fto,
 -                            m.getMapFromUnit().intValue(),
 -                            m.getMapToUnit().intValue());
 -                  }
 -                  List<BitSet> newgroups = new ArrayList<BitSet>();
 -                  if (xmlmat.getGroups().size() > 0)
 -                  {
 -                    for (String sgroup : xmlmat.getGroups())
 -                    {
 -                      newgroups.add(deStringifyBitset(sgroup));
 -                    }
 -                  }
 -                  String nwk = xmlmat.getNewick().size() > 0
 -                          ? xmlmat.getNewick().get(0)
 -                          : null;
 -                  if (xmlmat.getNewick().size() > 1)
 -                  {
 -                    Console.log.info(
 -                            "Ignoring additional clusterings for contact matrix");
 -                  }
 -                  String treeMethod = xmlmat.getTreeMethod();
 -                  double thresh = xmlmat.getCutHeight() != null
 -                          ? xmlmat.getCutHeight()
 -                          : 0;
 -                  GroupSet grpset = new GroupSet();
 -                  grpset.restoreGroups(newgroups, treeMethod, nwk, thresh);
 -                  PAEContactMatrix newpae = new PAEContactMatrix(
 -                          jaa.sequenceRef, mapping, elements, grpset);
 -                  jaa.sequenceRef.addContactListFor(jaa, newpae);
 -                }
 -              }
 -              else
 -              {
 -                Console.error("Ignoring CONTACT_MAP annotation with type "
 -                        + xmlmat.getType());
 -              }
 -            }
 +              restoreMatrixFor(jaa.sequenceRef, jaa, xmlmat);
 +            } 
            }
          }
  
        // XML.
        // and then recover its containing af to allow the settings to be applied.
        // TODO: fix for vamsas demo
-       System.err.println(
+       jalview.bin.Console.errPrintln(
                "About to recover a viewport for existing alignment: Sequence set ID is "
                        + uniqueSeqSetId);
        Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
          if (seqsetobj instanceof String)
          {
            uniqueSeqSetId = (String) seqsetobj;
-           System.err.println(
+           jalview.bin.Console.errPrintln(
                    "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
                            + uniqueSeqSetId);
          }
          else
          {
-           System.err.println(
+           jalview.bin.Console.errPrintln(
                    "Warning : Collision between sequence set ID string and existing jalview object mapping.");
          }
  
      return af;
    }
  
 +  private void importMatrixData(List<MatrixType> xmlmatrices)
 +  {
 +    for (MatrixType xmlmat:xmlmatrices)
 +    {
 +      if (!PAEContactMatrix.PAEMATRIX.equals(xmlmat.getType()))
 +      {
 +        Console.error("Ignoring matrix '"+xmlmat.getId()+"' of type '"+xmlmat.getType());
 +        continue;
 +      }
 +
 +      if (!xmlmat.getRows().equals(xmlmat.getCols()))
 +      {
 +        Console.error("Can't handle non square matrices");
 +        continue;
 +      }
 +
 +      float[][] elements = ContactMatrix
 +              .fromFloatStringToContacts(xmlmat.getElements(),
 +                      xmlmat.getCols().intValue(),
 +                      xmlmat.getRows().intValue());
 +      
 +      List<BitSet> newgroups = new ArrayList<BitSet>();
 +      if (xmlmat.getGroups().size() > 0)
 +      {
 +        for (String sgroup : xmlmat.getGroups())
 +        {
 +          newgroups.add(deStringifyBitset(sgroup));
 +        }
 +      }
 +      String nwk = xmlmat.getNewick().size() > 0
 +              ? xmlmat.getNewick().get(0)
 +              : null;
 +      if (xmlmat.getNewick().size() > 1)
 +      {
 +        Console.log.info(
 +                "Ignoring additional clusterings for contact matrix");
 +      }
 +      String treeMethod = xmlmat.getTreeMethod();
 +      double thresh = xmlmat.getCutHeight() != null
 +              ? xmlmat.getCutHeight()
 +              : 0;
 +      GroupSet grpset = new GroupSet();
 +      grpset.restoreGroups(newgroups, treeMethod, nwk, thresh);
 +      
 +      FloatContactMatrix newcm = new FloatContactMatrix(elements, grpset);
 +      contactMatrixRefs.put(xmlmat.getId(), newcm);
 +      Console.trace("Restored base contact matrix "+xmlmat.getId());
 +    }
 +  }
 +
 +  private void restoreMatrixFor(SequenceI sequenceRef,
 +          AlignmentAnnotation jaa, MapOnAMatrixType xmlmatmapping)
 +  {    
 +    // restore mapping data to matrix data
 +    jalview.util.MapList mapping = null;
 +    if (xmlmatmapping.getMapping() != null)
 +    {
 +      MapListType m = xmlmatmapping.getMapping();
 +      // Mapping m = dr.getMapping();
 +      int fr[] = new int[m.getMapListFrom().size() * 2];
 +      Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
 +      for (int _i = 0; from.hasNext(); _i += 2)
 +      {
 +        MapListFrom mf = from.next();
 +        fr[_i] = mf.getStart();
 +        fr[_i + 1] = mf.getEnd();
 +      }
 +      int fto[] = new int[m.getMapListTo().size() * 2];
 +      Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
 +      for (int _i = 0; to.hasNext(); _i += 2)
 +      {
 +        MapListTo mf = to.next();
 +        fto[_i] = mf.getStart();
 +        fto[_i + 1] = mf.getEnd();
 +      }
 +
 +      mapping = new jalview.util.MapList(fr, fto,
 +              m.getMapFromUnit().intValue(), m.getMapToUnit().intValue());
 +    }
 +    
 +    // locate matrix data in project XML and import
 +    ContactMatrixI cm = contactMatrixRefs.get(xmlmatmapping.getMatrix());
 +    if (cm == null)
 +    {
 +      frefedSequence
 +              .add(newMatrixFref(xmlmatmapping.getMatrix(), mapping, jaa));
 +    }
 +    else
 +    {
 +      // create the PAEMatrix now
 +      PAEContactMatrix newpae = new PAEContactMatrix(jaa.sequenceRef,
 +              mapping, cm);
 +
 +      jaa.sequenceRef.addContactListFor(jaa, newpae);
 +    }
 +
 +    return;
 +  }
 +
    /**
     * Load Overview window, restoring colours, 'show hidden regions' flag, title
     * and geometry as saved
          {
            if (tree.isColumnWise())
            {
-             AlignmentAnnotation aa = (AlignmentAnnotation) annotationIds
+             AlignmentAnnotation aa = annotationIds
                      .get(tree.getColumnReference());
              if (aa == null)
              {
          createOrLinkStructureViewer(entry, af, ap, jprovider);
        } catch (Exception e)
        {
-         System.err.println(
+         jalview.bin.Console.errPrintln(
                  "Error loading structure viewer: " + e.getMessage());
          // failed - try the next one
        }
              || version.equalsIgnoreCase("Test")
              || version.equalsIgnoreCase("AUTOMATED BUILD"))
      {
-       System.err.println("Assuming project file with "
+       jalview.bin.Console.errPrintln("Assuming project file with "
                + (version == null ? "null" : version)
                + " is compatible with Jalview version " + supported);
        return true;
      //
      // @Override
      // protected void processKeyEvent(java.awt.event.KeyEvent e) {
-     // System.out.println("Jalview2XML AF " + e);
+     // jalview.bin.Console.outPrintln("Jalview2XML AF " + e);
      // super.processKeyEvent(e);
      //
      // }
      }
      if (matchedAnnotation == null)
      {
-       System.err.println("Failed to match annotation colour scheme for "
-               + annotationId);
+       jalview.bin.Console
+               .errPrintln("Failed to match annotation colour scheme for "
+                       + annotationId);
        return null;
      }
      // belt-and-braces create a threshold line if the
          }
          // TODO: merges will never happen if we 'know' we have the real dataset
          // sequence - this should be detected when id==dssid
-         System.err.println(
+         jalview.bin.Console.errPrintln(
                  "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
          // + (pre ? "prepended" : "") + " "
          // + (post ? "appended" : ""));
        }
        else
        {
-         System.err.println(
+         jalview.bin.Console.errPrintln(
                  "Warning - making up dataset sequence id for DbRef sequence map reference");
          sqid = ((Object) ms).toString(); // make up a new hascode for
          // undefined dataset sequence hash
      } catch (IllegalStateException e)
      {
        // mixing AND and OR conditions perhaps
-       System.err.println(
+       jalview.bin.Console.errPrintln(
                String.format("Error reading filter conditions for '%s': %s",
                        featureType, e.getMessage()));
        // return as much as was parsed up to the error
        }
        else
        {
-         System.err.println("Malformed compound filter condition");
+         jalview.bin.Console
+                 .errPrintln("Malformed compound filter condition");
        }
      }
    }
      if (stateSavedUpToDate()) // nothing happened since last project save
        return true;
  
-     AlignFrame[] frames = Desktop.getAlignFrames();
+     AlignFrame[] frames = Desktop.getDesktopAlignFrames();
      if (frames != null)
      {
        for (int i = 0; i < frames.length; i++)
@@@ -49,6 -49,15 +49,15 @@@ public abstract class ContactMapRendere
       */
      Color selMinColor, selMaxColor;
  
+     /**
+      * 
+      * @param no_data - colour when no data available
+      * @param hidden - colour if this row is hidden
+      * @param maxColor - colour for maximum value of contact
+      * @param minColor - colour for minimum value of contact
+      * @param selMinColor - min colour if the contact has been selected
+      * @param selMaxColor - max colour if contact is selected
+      */
      public Shading(Color no_data, Color hidden, Color maxColor,
              Color minColor, Color selMinColor, Color selMaxColor)
      {
@@@ -79,8 -88,8 +88,8 @@@
        {
          return new Shading(Color.pink, Color.red,
  
-                 new Color(246, 252, 243), new Color(0, 60, 26),
-                 new Color(26, 0, 60), new Color(243, 246, 252));
+                 new Color(247, 252, 245), new Color(0, 68, 28),
+                 new Color(28, 0, 68), new Color(245,247,252));
        }
      };
    }
      int column;
      int aaMax = aa_annotations.length - 1;
      ContactMatrixI cm = viewport.getContactMatrix(_aa);
 +    if (cm==null)
 +    {
 +      return;
 +    }
      while (x < eRes - sRes)
      {
        column = sRes + x;
        for (int ht = 0, botY = topY
                - _aa.height; ht < _aa.graphHeight; ht += cgeom.pixels_step)
        {
-         ContactGeometry.contactInterval ci = cgeom.mapFor(ht,
-                 ht + cgeom.pixels_step);
+         ContactGeometry.contactInterval ci = cgeom.mapFor(ht);
          // cstart = (int) Math.floor(((double) y2 - ht) * contacts_per_pixel);
          // cend = (int) Math.min(contact_height,
          // Math.ceil(cstart + contacts_per_pixel * pixels_step));
  
          Color col;
-         boolean rowsel = false, containsHidden = false;
+         boolean rowsel = false;
+         boolean containsHidden = false;
          if (columnSelection != null)
          {
            rowsel = cgeom.intersects(ci, columnSelection, hiddenColumns,
          g.setColor(col);
          if (cgeom.pixels_step > 1)
          {
-           g.fillRect(x * charWidth, botY+ht, charWidth, 1 + cgeom.pixels_step);
+           g.fillRect(x * charWidth, botY+ht, charWidth, cgeom.pixels_step);
          }
          else
          {
    {
      ContactRange cr = cl.getRangeFor(i, j);
      // average for moment - probably more interested in maxIntProj though
-     return jalview.util.ColorUtils.getGraduatedColour((float) cr.getMean(),
+     return jalview.util.ColorUtils.getGraduatedColour((float) cr.getMin(),
              0, shade.selMinColor, max, shade.selMaxColor);
    }
  
@@@ -47,7 -47,6 +47,7 @@@ import org.testng.AssertJUnit
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
 +import jalview.analysis.AlignmentUtils;
  import jalview.analysis.scoremodels.SimilarityParams;
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
@@@ -67,7 -66,6 +67,7 @@@ import jalview.datamodel.HiddenSequence
  import jalview.datamodel.Mapping;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.PDBEntry.Type;
 +import jalview.datamodel.Sequence;
  import jalview.datamodel.Sequence.DBModList;
  import jalview.datamodel.SequenceCollectionI;
  import jalview.datamodel.SequenceFeature;
@@@ -289,12 -287,12 +289,12 @@@ public class Jalview2xmlTests extends J
    @Test(groups = { "Functional" })
    public void gatherViewsHere() throws Exception
    {
-     int origCount = Desktop.getAlignFrames() == null ? 0
-             : Desktop.getAlignFrames().length;
+     int origCount = Desktop.getDesktopAlignFrames() == null ? 0
+             : Desktop.getDesktopAlignFrames().length;
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
      assertNotNull(af, "Didn't read in the example file correctly.");
-     assertTrue(Desktop.getAlignFrames().length == 1 + origCount,
+     assertTrue(Desktop.getDesktopAlignFrames().length == 1 + origCount,
              "Didn't gather the views in the example file.");
  
    }
  
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
-     Assert.assertEquals(Desktop.getAlignFrames().length, 1);
+     Assert.assertEquals(Desktop.getDesktopAlignFrames().length, 1);
      String afid = af.getViewport().getSequenceSetId();
  
      // check FileLoader returned a reference to the one alignFrame that is
  
      Desktop.explodeViews(af);
  
-     int oldviews = Desktop.getAlignFrames().length;
-     Assert.assertEquals(Desktop.getAlignFrames().length,
+     int oldviews = Desktop.getDesktopAlignFrames().length;
+     Assert.assertEquals(Desktop.getDesktopAlignFrames().length,
              Desktop.getAlignmentPanels(afid).length);
      File tfile = File.createTempFile("testStoreAndRecoverExpanded", ".jvp");
      try
        Assert.fail("Didn't save the expanded view state", e);
      }
      Desktop.instance.closeAll_actionPerformed(null);
-     if (Desktop.getAlignFrames() != null)
+     if (Desktop.getDesktopAlignFrames() != null)
      {
-       Assert.assertEquals(Desktop.getAlignFrames().length, 0);
+       Assert.assertEquals(Desktop.getDesktopAlignFrames().length, 0);
      }
      af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
              DataSourceType.FILE);
      Assert.assertNotNull(af);
-     Assert.assertEquals(Desktop.getAlignFrames().length,
+     Assert.assertEquals(Desktop.getDesktopAlignFrames().length,
              Desktop.getAlignmentPanels(
                      af.getViewport().getSequenceSetId()).length);
      Assert.assertEquals(Desktop
        Assert.fail("Didn't save the expanded view state", e);
      }
      Desktop.instance.closeAll_actionPerformed(null);
-     if (Desktop.getAlignFrames() != null)
+     if (Desktop.getDesktopAlignFrames() != null)
      {
-       Assert.assertEquals(Desktop.getAlignFrames().length, 0);
+       Assert.assertEquals(Desktop.getDesktopAlignFrames().length, 0);
      }
  
      af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
        Assert.fail("Didn't save the expanded view state", e);
      }
      Desktop.instance.closeAll_actionPerformed(null);
-     if (Desktop.getAlignFrames() != null)
+     if (Desktop.getDesktopAlignFrames() != null)
      {
-       Assert.assertEquals(Desktop.getAlignFrames().length, 0);
+       Assert.assertEquals(Desktop.getDesktopAlignFrames().length, 0);
      }
  
      af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
        Assert.fail("Didn't save the state", e);
      }
      Desktop.instance.closeAll_actionPerformed(null);
-     if (Desktop.getAlignFrames() != null)
+     if (Desktop.getDesktopAlignFrames() != null)
      {
-       Assert.assertEquals(Desktop.getAlignFrames().length, 0);
+       Assert.assertEquals(Desktop.getDesktopAlignFrames().length, 0);
      }
  
      AlignFrame restoredFrame = new FileLoader().LoadFileWaitTillLoaded(
      assertNotNull(af);
  
      AlignmentI ds = null;
-     for (AlignFrame alignFrame : Desktop.getAlignFrames())
+     for (AlignFrame alignFrame : Desktop.getDesktopAlignFrames())
      {
        if (ds == null)
        {
    }
  
    @Test(groups = { "Functional" })
 +  public void testMatrixToFloatsAndBack()
 +  {
 +    int imax=2000;
 +    int i=imax;
 +    SequenceI sq = new Sequence("dummy","SEQ");
 +    while (sq.getLength()<i)
 +    {
 +      sq.setSequence(sq.getSequenceAsString()+'Q');
 +    }
 +    float[][] paevals = new float[i][i];
 +    for (i = imax - 1; i >= 0; i--)
 +    {
 +      for (int j = 0; j <= i; j++)
 +      {
 +        paevals[i][j] = ((i - j < 2)
 +                || ((i > 1 && i < 5) && (j > 1 && i < 5))) ? 1 : 0f;
 +        paevals[j][i] = -paevals[i][j];
 +      }
 +    }
 +    PAEContactMatrix dummyMat = new PAEContactMatrix(sq, paevals);
 +    String content = ContactMatrix.contactToFloatString(dummyMat);
 +    Assert.assertTrue(content.contains("\t1.")); // at least one element must be
 +                                                 // 1
 +    float[][] vals = ContactMatrix.fromFloatStringToContacts(content,
 +            sq.getLength(), sq.getLength());
 +    assertEquals(vals[3][4], paevals[3][4]);
 +    assertEquals(vals[4][3], paevals[4][3]);
 +    
 +    // test recovery
 +    for (i=0;i<imax;i++)
 +    {
 +      for (int j=0;j<imax;j++)
 +      {
 +        assertEquals(vals[i][j],paevals[i][j]);
 +      }
 +    }
 +  }
 +  @Test(groups = { "Functional" })
    public void testPAEsaveRestore() throws Exception
    {
      Desktop.instance.closeAll_actionPerformed(null);
      Assert.assertEquals(restoredMat.getGroups(), dummyMat.getGroups());
      Assert.assertEquals(restoredMat.hasTree(), dummyMat.hasTree());
      Assert.assertEquals(restoredMat.getNewick(), dummyMat.getNewick());
 +
 +    // verify no duplicate PAE matrix data when new view created and saved
 +    
 +    // add reference annotations to view first, then copy
 +    AlignmentUtils.addReferenceAnnotationTo(newAl, newAl.getSequenceAt(0), newSeq.getAnnotation()[0],null);
 +    
 +    AlignmentViewPanel newview = af.newView("copy of PAE", true);
 +    
 +    // redundant asserts here check all is good with the new view firest...
 +    AlignmentI newviewAl = newview.getAlignment();
 +    SequenceI newviewSeq = newviewAl.getSequenceAt(0);
 +    // check annotation of the expected type exists
 +    Assert.assertEquals(newviewSeq.getAnnotation().length, 1);
 +    Assert.assertEquals(newviewSeq.getAnnotation()[0].graph, paeCm.graph);
 +    // check we have just one contact matrix mapping
 +    Assert.assertEquals(newviewSeq.getContactMaps().size(), 1);
 +    
 +    // and can be found for the annotation on the sequence
 +    ContactMatrixI newviewMat = newviewSeq
 +            .getContactMatrixFor(newviewSeq.getAnnotation()[0]);
 +    Assert.assertNotNull(newviewMat);
 +
 +    Assert.assertTrue(newviewMat == restoredMat);
 +    
 +    // save the two views and restore. Now look at visible annotation to check all views have shared refs.
 +    
 +    tfile = File.createTempFile("testStoreAndRecoverPAEmatrixTwoViews",
 +            ".jvp");
 +    new Jalview2XML(false).saveState(tfile);
 +    Desktop.instance.closeAll_actionPerformed(null);
 +
 +    af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
 +            DataSourceType.FILE);
 +    newAl = af.getAlignPanels().get(0).getAlignment();
 +    AlignmentAnnotation view1aa = newAl.getSequenceAt(0).getAnnotation()[0];
 +
 +    newviewAl = af.getAlignPanels().get(1).getAlignment();
 +    AlignmentAnnotation view2aa = newviewAl.getSequenceAt(0).getAnnotation()[0];
 +
 +    // annotations are shared across alignment views - so should still have an identical pair of annotations.
 +    Assert.assertTrue(view1aa==view2aa);
 +    // identical annotations means identical contact matrix mappings
 +    Assert.assertEquals(newAl.getDataset().getSequenceAt(0).getContactMaps().size(), 1);
 +
 +    // TODO Verify when distinct mappable PAEs are created, only one PAE dataset is actually held.
 +    // Assert.assertTrue(view1aa!=view2aa);
 +    // restoredMat = newAl.getContactMatrixFor(view1aa);
 +    // newviewMat = newviewAl.getContactMatrixFor(view2aa);
 +    // Assert.assertTrue(restoredMat!=newviewMat);
 +    
    }
  
  }