JAL-4134 JAL-3855 store/restore groups, tree and threshold used to cluster a PAE...
[jalview.git] / src / jalview / project / Jalview2XML.java
index 8877af2..36c87ce 100644 (file)
@@ -26,6 +26,7 @@ import static jalview.math.RotatableMatrix.Axis.Z;
 
 import java.awt.Color;
 import java.awt.Font;
+import java.awt.FontMetrics;
 import java.awt.Rectangle;
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
@@ -44,6 +45,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.GregorianCalendar;
@@ -89,6 +91,8 @@ import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ContactMatrix;
+import jalview.datamodel.ContactMatrixI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.GeneLocus;
 import jalview.datamodel.GraphLine;
@@ -112,6 +116,7 @@ import jalview.gui.AppVarna;
 import jalview.gui.Desktop;
 import jalview.gui.JvOptionPane;
 import jalview.gui.OOMWarning;
+import jalview.gui.OverviewPanel;
 import jalview.gui.PCAPanel;
 import jalview.gui.PaintRefresher;
 import jalview.gui.SplitFrame;
@@ -147,6 +152,7 @@ import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
+import jalview.ws.datamodel.alphafold.PAEContactMatrix;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.dm.AAConSettings;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
@@ -185,11 +191,13 @@ import jalview.xml.binding.jalview.JalviewModel.UserColours;
 import jalview.xml.binding.jalview.JalviewModel.Viewport;
 import jalview.xml.binding.jalview.JalviewModel.Viewport.CalcIdParam;
 import jalview.xml.binding.jalview.JalviewModel.Viewport.HiddenColumns;
+import jalview.xml.binding.jalview.JalviewModel.Viewport.Overview;
 import jalview.xml.binding.jalview.JalviewUserColours;
 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
 import jalview.xml.binding.jalview.MapListType.MapListFrom;
 import jalview.xml.binding.jalview.MapListType.MapListTo;
 import jalview.xml.binding.jalview.Mapping;
+import jalview.xml.binding.jalview.MatrixType;
 import jalview.xml.binding.jalview.NoValueColour;
 import jalview.xml.binding.jalview.ObjectFactory;
 import jalview.xml.binding.jalview.PcaDataType;
@@ -229,6 +237,11 @@ public class Jalview2XML
   private static final String UTF_8 = "UTF-8";
 
   /**
+   * used in decision if quit confirmation should be issued
+   */
+  private static boolean stateSavedUpToDate = false;
+
+  /**
    * prefix for recovering datasets for alignments with multiple views where
    * non-existent dataset IDs were written for some views
    */
@@ -616,6 +629,27 @@ public class Jalview2XML
   {
     AlignFrame[] frames = Desktop.getAlignFrames();
 
+    setStateSavedUpToDate(true);
+
+    if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
+    {
+      int n = debugDelaySave;
+      int i = 0;
+      while (i < n)
+      {
+        Console.debug("***** debugging save sleep " + i + "/" + n);
+        try
+        {
+          Thread.sleep(1000);
+        } catch (InterruptedException e)
+        {
+          // TODO Auto-generated catch block
+          e.printStackTrace();
+        }
+        i++;
+      }
+    }
+
     if (frames == null)
     {
       return;
@@ -763,6 +797,25 @@ public class Jalview2XML
       FileOutputStream fos = new FileOutputStream(
               doBackup ? backupfiles.getTempFilePath() : jarFile);
 
+      if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
+      {
+        int n = debugDelaySave;
+        int i = 0;
+        while (i < n)
+        {
+          Console.debug("***** debugging save sleep " + i + "/" + n);
+          try
+          {
+            Thread.sleep(1000);
+          } catch (InterruptedException e)
+          {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+          }
+          i++;
+        }
+      }
+
       JarOutputStream jout = new JarOutputStream(fos);
       List<AlignFrame> frames = new ArrayList<>();
 
@@ -882,8 +935,7 @@ public class Jalview2XML
     {
       System.err.println("error writing date: " + e.toString());
     }
-    object.setVersion(
-            Cache.getDefault("VERSION", "Development Build"));
+    object.setVersion(Cache.getDefault("VERSION", "Development Build"));
 
     /**
      * rjal is full height alignment, jal is actual alignment with full metadata
@@ -1448,6 +1500,23 @@ public class Jalview2XML
       view.setStartRes(vpRanges.getStartRes());
       view.setStartSeq(vpRanges.getStartSeq());
 
+      OverviewPanel ov = ap.getOverviewPanel();
+      if (ov != null)
+      {
+        Overview overview = new Overview();
+        overview.setTitle(ov.getTitle());
+        Rectangle bounds = ov.getFrameBounds();
+        overview.setXpos(bounds.x);
+        overview.setYpos(bounds.y);
+        overview.setWidth(bounds.width);
+        overview.setHeight(bounds.height);
+        overview.setShowHidden(ov.isShowHiddenRegions());
+        overview.setGapColour(ov.getCanvas().getGapColour().getRGB());
+        overview.setResidueColour(
+                ov.getCanvas().getResidueColour().getRGB());
+        overview.setHiddenColour(ov.getCanvas().getHiddenColour().getRGB());
+        view.setOverview(overview);
+      } 
       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
       {
         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
@@ -1488,6 +1557,8 @@ public class Jalview2XML
 
       view.setConservationSelected(av.getConservationSelected());
       view.setPidSelected(av.getAbovePIDThreshold());
+      view.setCharHeight(av.getCharHeight());
+      view.setCharWidth(av.getCharWidth());
       final Font font = av.getFont();
       view.setFontName(font.getName());
       view.setFontSize(font.getSize());
@@ -1633,7 +1704,8 @@ public class Jalview2XML
                 .getHiddenColumns();
         if (hidden == null)
         {
-          Console.warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
+          Console.warn(
+                  "REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
         }
         else
         {
@@ -2225,6 +2297,43 @@ public class Jalview2XML
           line.setColour(annotation.getThreshold().colour.getRGB());
           an.setThresholdLine(line);
         }
+        if (annotation.graph==AlignmentAnnotation.CONTACT_MAP)
+        {
+          if (annotation.sequenceRef.getContactMaps()!=null)
+          {
+            ContactMatrixI cm = annotation.sequenceRef.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())
+                {
+                  BigInteger val = new BigInteger(gp.toByteArray());
+                  xmlmat.getGroups().add(val.toString());
+                }
+              }
+              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
+              an.getContactmatrix().add(xmlmat);
+            }
+          }
+        }
       }
       else
       {
@@ -2255,10 +2364,9 @@ public class Jalview2XML
       {
         for (String pr : annotation.getProperties())
         {
-          jalview.xml.binding.jalview.Annotation.Property prop = new jalview.xml.binding.jalview.Annotation.Property();
+          jalview.xml.binding.jalview.Property prop = new jalview.xml.binding.jalview.Property();
           prop.setName(pr);
           prop.setValue(annotation.getProperty(pr));
-          // an.addProperty(prop);
           an.getProperty().add(prop);
         }
       }
@@ -2418,7 +2526,8 @@ public class Jalview2XML
       }
       else
       {
-        Console.warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
+        Console.warn(
+                "Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
         return false;
       }
     }
@@ -2462,7 +2571,8 @@ public class Jalview2XML
         return id.toString();
       }
       // give up and warn that something has gone wrong
-      Console.warn("Cannot find ID for object in external mapping : " + jvobj);
+      Console.warn(
+              "Cannot find ID for object in external mapping : " + jvobj);
     }
     return altCode;
   }
@@ -3230,7 +3340,8 @@ public class Jalview2XML
       }
       else
       {
-        Console.warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
+        Console.warn(
+                "Couldn't find entry in Jalview Jar for " + jarEntryName);
       }
     } catch (Exception ex)
     {
@@ -3830,11 +3941,69 @@ public class Jalview2XML
         jaa.setCalcId(annotation.getCalcId());
         if (annotation.getProperty().size() > 0)
         {
-          for (Annotation.Property prop : annotation.getProperty())
+          for (jalview.xml.binding.jalview.Property prop : annotation.getProperty())
           {
             jaa.setProperty(prop.getName(), prop.getValue());
           }
         }
+        if (jaa.graph == AlignmentAnnotation.CONTACT_MAP)
+        {
+          if (annotation.getContactmatrix() != null
+                  && annotation.getContactmatrix().size() > 0)
+          {
+            for (MatrixType 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());
+
+                  PAEContactMatrix newpae = new PAEContactMatrix(
+                          jaa.sequenceRef, elements);
+                  List<BitSet> newgroups=new ArrayList<BitSet>();
+                  if (xmlmat.getGroups().size()>0)
+                  {
+                    for (String sgroup:xmlmat.getGroups())
+                    {
+                      try {
+                        BigInteger group = new BigInteger(sgroup);
+                        newgroups.add(BitSet.valueOf(group.toByteArray()));
+                      } catch (NumberFormatException nfe)
+                      {
+                        Console.error("Problem parsing groups for a contact matrix (\""+sgroup+"\"",nfe);
+                      }
+                    }
+                  }
+                  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;
+                  newpae.restoreGroups(newgroups, treeMethod, nwk, thresh);
+                  jaa.sequenceRef.addContactListFor(jaa, newpae);
+                }
+              }
+              else
+              {
+                Console.error("Ignoring CONTACT_MAP annotation with type "
+                        + xmlmat.getType());
+              }
+            }
+          }
+        }
+
         if (jaa.autoCalculated)
         {
           autoAlan.add(new JvAnnotRow(i, jaa));
@@ -4045,7 +4214,7 @@ public class Jalview2XML
     }
 
     /*
-     * Load any trees, PDB structures and viewers
+     * Load any trees, PDB structures and viewers, Overview
      * 
      * Not done if flag is false (when this method is used for New View)
      */
@@ -4055,12 +4224,49 @@ public class Jalview2XML
       loadPCAViewers(jalviewModel, ap);
       loadPDBStructures(jprovider, jseqs, af, ap);
       loadRnaViewers(jprovider, jseqs, ap);
+      loadOverview(view, jalviewModel.getVersion(), af);
     }
     // and finally return.
     return af;
   }
 
   /**
+   * Load Overview window, restoring colours, 'show hidden regions' flag, title
+   * and geometry as saved
+   * 
+   * @param view
+   * @param af
+   */
+  protected void loadOverview(Viewport view, String version, AlignFrame af)
+  {
+    if (!isVersionStringLaterThan("2.11.3",
+            version) && view.getOverview()==null)
+    {
+      return;
+    }
+    /*
+     * first close any Overview that was opened automatically
+     * (if so configured in Preferences) so that the view is
+     * restored in the same state as saved
+     */
+    af.alignPanel.closeOverviewPanel();
+
+    Overview overview = view.getOverview();
+    if (overview != null)
+    {
+      OverviewPanel overviewPanel = af
+              .openOverviewPanel(overview.isShowHidden());
+      overviewPanel.setTitle(overview.getTitle());
+      overviewPanel.setFrameBounds(overview.getXpos(), overview.getYpos(),
+              overview.getWidth(), overview.getHeight());
+      Color gap = new Color(overview.getGapColour());
+      Color residue = new Color(overview.getResidueColour());
+      Color hidden = new Color(overview.getHiddenColour());
+      overviewPanel.getCanvas().setColours(gap, residue, hidden);
+    }
+  }
+
+  /**
    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
    * panel is restored from separate jar entries, two (gapped and trimmed) per
    * sequence and secondary structure.
@@ -4205,8 +4411,9 @@ public class Jalview2XML
         tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
         if (tp == null)
         {
-          Console.warn("There was a problem recovering stored Newick tree: \n"
-                  + tree.getNewick());
+          Console.warn(
+                  "There was a problem recovering stored Newick tree: \n"
+                          + tree.getNewick());
           continue;
         }
 
@@ -4576,7 +4783,7 @@ public class Jalview2XML
    *          - minimum version we are comparing against
    * @param version
    *          - version of data being processsed
-   * @return
+   * @return true if version is equal to or later than supported
    */
   public static boolean isVersionStringLaterThan(String supported,
           String version)
@@ -4723,7 +4930,12 @@ public class Jalview2XML
     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
     viewport.setFont(new Font(view.getFontName(),
             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
-            true);
+            (view.getCharWidth()!=null) ? false : true);
+    if (view.getCharWidth()!=null)
+    {
+      viewport.setCharWidth(view.getCharWidth());
+      viewport.setCharHeight(view.getCharHeight());
+    }
     ViewStyleI vs = viewport.getViewStyle();
     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
     viewport.setViewStyle(vs);
@@ -4982,6 +5194,7 @@ public class Jalview2XML
     {
       splitFrameCandidates.put(view, af);
     }
+
     return af;
   }
 
@@ -5045,7 +5258,10 @@ public class Jalview2XML
               + annotationId);
       return null;
     }
-    if (matchedAnnotation.getThreshold() == null)
+    // belt-and-braces create a threshold line if the 
+    // colourscheme needs one but the matchedAnnotation doesn't have one
+    if (safeInt(viewAnnColour.getAboveThreshold()) != 0
+            && matchedAnnotation.getThreshold() == null)
     {
       matchedAnnotation.setThreshold(
               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
@@ -5282,14 +5498,16 @@ public class Jalview2XML
       {
         if (ds != null && ds != seqSetDS)
         {
-          Console.warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
-                  + " - CDS/Protein crossreference data may be lost");
+          Console.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.
-            Console.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.");
+            Console.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;
@@ -5573,7 +5791,8 @@ public class Jalview2XML
   {
     if (dataset.getDataset() != null)
     {
-      Console.warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
+      Console.warn(
+              "Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
     }
     String datasetId = makeHashCode(dataset, null);
     if (datasetId == null)
@@ -5905,7 +6124,8 @@ public class Jalview2XML
       }
       else
       {
-        Console.warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
+        Console.warn(
+                "Couldn't find entry in Jalview Jar for " + jarEntryName);
       }
     } catch (Exception ex)
     {
@@ -6529,4 +6749,44 @@ public class Jalview2XML
 
     return colour;
   }
+
+  public static void setStateSavedUpToDate(boolean s)
+  {
+    Console.debug("Setting overall stateSavedUpToDate to " + s);
+    stateSavedUpToDate = s;
+  }
+
+  public static boolean stateSavedUpToDate()
+  {
+    Console.debug("Returning overall stateSavedUpToDate value: "
+            + stateSavedUpToDate);
+    return stateSavedUpToDate;
+  }
+
+  public static boolean allSavedUpToDate()
+  {
+    if (stateSavedUpToDate()) // nothing happened since last project save
+      return true;
+
+    AlignFrame[] frames = Desktop.getAlignFrames();
+    if (frames != null)
+    {
+      for (int i = 0; i < frames.length; i++)
+      {
+        if (frames[i] == null)
+          continue;
+        if (!frames[i].getViewport().savedUpToDate())
+          return false; // at least one alignment is not individually saved
+      }
+    }
+    return true;
+  }
+
+  // used for debugging and tests
+  private static int debugDelaySave = 20;
+
+  public static void setDebugDelaySave(int n)
+  {
+    debugDelaySave = n;
+  }
 }