JAL-3484 temporary override of autocalc preferences for project load
[jalview.git] / src / jalview / project / Jalview2XML.java
index f772cf5..d80fd86 100644 (file)
@@ -24,6 +24,8 @@ import static jalview.math.RotatableMatrix.Axis.X;
 import static jalview.math.RotatableMatrix.Axis.Y;
 import static jalview.math.RotatableMatrix.Axis.Z;
 
+import jalview.analysis.AnnotationSorter;
+import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.analysis.Conservation;
 import jalview.analysis.PCA;
 import jalview.analysis.scoremodels.ScoreModels;
@@ -90,6 +92,7 @@ import jalview.util.StringUtils;
 import jalview.util.jarInputStreamProvider;
 import jalview.util.matcher.Condition;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.AlignmentViewport.AutoAnnotation;
 import jalview.viewmodel.PCAModel;
 import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
@@ -197,17 +200,21 @@ import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamReader;
 
 /**
- * Write out the current jalview desktop state as a Jalview XML stream.
- * 
- * Note: the vamsas objects referred to here are primitive versions of the
- * VAMSAS project schema elements - they are not the same and most likely never
- * will be :)
- * 
- * @author $author$
- * @version $Revision: 1.134 $
+ * Provides methods to read in or write out the Jalview desktop state as a
+ * Jalview XML model, as one or more entries in a .jar archive. The jar file may
+ * include additional data file entries, such as
+ * <ul>
+ * <li>PDB structure data files</li>
+ * <li>session state for one or more Jmol, Chimera or Varna structure
+ * viewers</li>
+ * </ul>
  */
 public class Jalview2XML
 {
+  private static final String TRUE = "true";
+
+  private static final String FALSE = "false";
+
   private static final String VIEWER_PREFIX = "viewer_";
 
   private static final String RNA_PREFIX = "rna_";
@@ -1479,6 +1486,8 @@ public class Jalview2XML
       view.setRenderGaps(av.isRenderGaps());
       view.setShowAnnotation(av.isShowAnnotation());
       view.setShowBoxes(av.getShowBoxes());
+      view.setShowAutocalcAbove(av.isShowAutocalculatedAbove());
+      view.setSortAnnotationsBy(av.getSortAnnotationsBy().name());
       view.setShowColourText(av.getColourText());
       view.setShowFullId(av.getShowJVSuffix());
       view.setRightAlignIds(av.isRightAlignIds());
@@ -3619,7 +3628,7 @@ public class Jalview2XML
 
     // ////////////////////////////////
     // LOAD ANNOTATIONS
-    List<JvAnnotRow> autoAlan = new ArrayList<>();
+    List<AlignmentAnnotation> addedAnnotation = new ArrayList<>();
 
     /*
      * store any annotations which forward reference a group's ID
@@ -3638,17 +3647,14 @@ public class Jalview2XML
          * test if annotation is automatically calculated for this view only
          */
         boolean autoForView = false;
-        if (annotation.getLabel().equals("Quality")
-                || annotation.getLabel().equals("Conservation")
-                || annotation.getLabel().equals("Consensus"))
+        if (annotation.getLabel().equals(AutoAnnotation.QUALITY.label)
+                || annotation.getLabel()
+                        .equals(AutoAnnotation.CONSERVATION.label)
+                || annotation.getLabel()
+                        .equals(AutoAnnotation.CONSENSUS.label))
         {
           // Kludge for pre 2.5 projects which lacked the autocalculated flag
           autoForView = true;
-          // JAXB has no has() test; schema defaults value to false
-          // if (!annotation.hasAutoCalculated())
-          // {
-          // annotation.setAutoCalculated(true);
-          // }
         }
         if (autoForView || annotation.isAutoCalculated())
         {
@@ -3670,7 +3676,7 @@ public class Jalview2XML
           }
 
           al.addAnnotation(jda);
-
+          addedAnnotation.add(jda);
           continue;
         }
         // Construct new annotation from model.
@@ -3710,7 +3716,11 @@ public class Jalview2XML
             }
           }
         }
-        jalview.datamodel.AlignmentAnnotation jaa = null;
+
+        /*
+         * construct the Jalview AlignmentAnnotation, add to alignment
+         */
+        AlignmentAnnotation jaa = null;
 
         if (annotation.isGraph())
         {
@@ -3808,9 +3818,10 @@ public class Jalview2XML
           jaa.autoCalculated = true; // means annotation will be marked for
           // update at end of load.
         }
-        if (annotation.getGraphHeight() != null)
+        Integer graphHeight = annotation.getGraphHeight();
+        if (graphHeight != null)
         {
-          jaa.graphHeight = annotation.getGraphHeight().intValue();
+          jaa.graphHeight = graphHeight.intValue();
         }
         jaa.belowAlignment = annotation.isBelowAlignment();
         jaa.setCalcId(annotation.getCalcId());
@@ -3822,17 +3833,16 @@ public class Jalview2XML
             jaa.setProperty(prop.getName(), prop.getValue());
           }
         }
-        if (jaa.autoCalculated)
+        if (!jaa.autoCalculated)
         {
-          autoAlan.add(new JvAnnotRow(i, jaa));
-        }
-        else
-        // if (!autoForView)
-        {
-          // add autocalculated group annotation and any user created annotation
-          // for the view
+          // TODO ensure Consensus etc is enabled if found in project?
+          /*
+           * add autocalculated group annotation and any user created annotation
+           * for the view
+           */
           al.addAnnotation(jaa);
         }
+        addedAnnotation.add(jaa);
       }
     }
     // ///////////////////////
@@ -3859,7 +3869,7 @@ public class Jalview2XML
           }
           else
           {
-            cs = ColourSchemeProperty.getColourScheme(al,
+            cs = ColourSchemeProperty.getColourScheme(null, al,
                     jGroup.getColour());
           }
         }
@@ -4024,10 +4034,25 @@ public class Jalview2XML
 
     if (isnewview)
     {
-      af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
-              uniqueSeqSetId, viewId, autoAlan);
+      Map<String, String> originalPreferences = null;
+      try
+      {
+        originalPreferences = setAutocalcPreferences(jalviewModel);
+        af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
+                uniqueSeqSetId, viewId);
+      } finally
+      {
+        restoreAutocalcPreferences(originalPreferences);
+      }
+
+      /*
+       * resort annotations to their order in the project
+       * (also sets height and visibility for autocalc'd annotation)
+       */
       av = af.getViewport();
+      new AnnotationSorter(av).sort(addedAnnotation);
       ap = af.alignPanel;
+      ap.adjustAnnotationHeight();
     }
 
     /*
@@ -4047,6 +4072,93 @@ public class Jalview2XML
   }
 
   /**
+   * Restores previously captured preferences (or deletes the preference if the
+   * map entry value is null)
+   * 
+   * @param originalPreferences
+   */
+  private void restoreAutocalcPreferences(
+          Map<String, String> originalPreferences)
+  {
+    for (Entry<String, String> entry : originalPreferences.entrySet())
+    {
+      String key = entry.getKey();
+      String value = entry.getValue();
+      if (value == null)
+      {
+        Cache.removeProperty(key);
+      }
+      else
+      {
+        Cache.setProperty(key, value);
+      }
+    }
+  }
+
+  /**
+   * Inspects saved annotations and temporarily sets preferences for whether
+   * autocalculated annotations should be created for Conservation, Consensus,
+   * Quality, Occupancy. Returns a map containing the original values (which may
+   * be null if property is not set, or is set to null).
+   * 
+   * @param jalviewModel
+   * @return
+   */
+  private Map<String, String> setAutocalcPreferences(
+          JalviewModel jalviewModel)
+  {
+    Map<String, String> original = Cache.getProperties(
+            AutoAnnotation.OCCUPANCY.preferenceKey,
+            AutoAnnotation.CONSERVATION.preferenceKey,
+            AutoAnnotation.QUALITY.preferenceKey,
+            AutoAnnotation.CONSENSUS.preferenceKey);
+
+    Cache.setProperty(AutoAnnotation.OCCUPANCY.preferenceKey, FALSE);
+    Cache.setProperty(AutoAnnotation.CONSERVATION.preferenceKey, FALSE);
+    Cache.setProperty(AutoAnnotation.QUALITY.preferenceKey, FALSE);
+    Cache.setProperty(AutoAnnotation.CONSENSUS.preferenceKey, FALSE);
+
+    List<SequenceSet> sequenceSet = jalviewModel.getVamsasModel()
+            .getSequenceSet();
+
+    /*
+     * expect sequenceSet to have just one entry
+     */
+    if (sequenceSet.size() != 1)
+    {
+      System.err.println(
+              "Unexpected sequenceSet size: " + sequenceSet.size());
+      return original;
+    }
+    List<Annotation> anns = sequenceSet.get(0).getAnnotation();
+    for (Annotation ann : anns)
+    {
+      if (ann.isAutoCalculated())
+      {
+        String label = ann.getLabel();
+        if (AutoAnnotation.CONSERVATION.label.equals(label))
+        {
+          Cache.setProperty(AutoAnnotation.CONSERVATION.preferenceKey,
+                  TRUE);
+        }
+        else if (AutoAnnotation.QUALITY.label.equals(label))
+        {
+          Cache.setProperty(AutoAnnotation.QUALITY.preferenceKey, TRUE);
+        }
+        else if (AutoAnnotation.CONSENSUS.label.equals(label))
+        {
+          Cache.setProperty(AutoAnnotation.CONSENSUS.preferenceKey, TRUE);
+        }
+        else if (AutoAnnotation.OCCUPANCY.label.equals(label))
+        {
+          Cache.setProperty(AutoAnnotation.OCCUPANCY.preferenceKey, TRUE);
+        }
+      }
+    }
+    return original;
+  }
+
+  /**
    * 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.
@@ -4187,10 +4299,8 @@ public class Jalview2XML
           // TODO: verify 'associate with all views' works still
           tp.getTreeCanvas().setViewport(av); // af.viewport;
           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
-          // FIXME: should we use safeBoolean here ?
-          tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
-
         }
+        tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
         if (tp == null)
         {
           warn("There was a problem recovering stored Newick tree: \n"
@@ -4576,7 +4686,7 @@ public class Jalview2XML
       {
         if (val.contains("e")) // eh? what can it be?
         {
-          if (val.trim().equals("true"))
+          if (val.trim().equals(TRUE))
           {
             val = "1";
           }
@@ -4837,7 +4947,7 @@ public class Jalview2XML
   AlignFrame loadViewport(String file, List<JSeq> JSEQ,
           List<SequenceI> hiddenSeqs, AlignmentI al,
           JalviewModel jm, Viewport view, String uniqueSeqSetId,
-          String viewId, List<JvAnnotRow> autoAlan)
+          String viewId)
   {
     AlignFrame af = null;
     af = new AlignFrame(al, safeInt(view.getWidth()),
@@ -4918,9 +5028,8 @@ public class Jalview2XML
 
     viewport.setColourText(safeBoolean(view.isShowColourText()));
 
-    viewport
-            .setConservationSelected(
-                    safeBoolean(view.isConservationSelected()));
+    viewport.setConservationSelected(
+            safeBoolean(view.isConservationSelected()));
     viewport.setIncrement(safeInt(view.getConsThreshold()));
     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
@@ -4936,10 +5045,27 @@ public class Jalview2XML
     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
 
-    viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
+    Boolean autocalcFirst = view.isShowAutocalcAbove();
+    if (autocalcFirst != null)
+    {
+      af.setShowAutoCalculatedAbove(autocalcFirst.booleanValue());
+    }
+    String sortBy = view.getSortAnnotationsBy();
+    if (sortBy != null)
+    {
+      try
+      {
+        viewport.setSortAnnotationsBy(
+                SequenceAnnotationOrder.valueOf(sortBy));
+      } catch (IllegalArgumentException e)
+      {
+        Cache.log.error(
+                "Invalid annotation sort specifier in project: " + sortBy);
+      }
+    }
 
+    viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
     viewport.setShowText(safeBoolean(view.isShowText()));
-
     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
@@ -4973,25 +5099,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
@@ -5067,7 +5195,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)
           {
@@ -5175,7 +5304,6 @@ public class Jalview2XML
               safeInt(view.getWidth()), safeInt(view.getHeight()));
       // recompute any autoannotation
       af.alignPanel.updateAnnotation(false, true);
-      reorderAutoannotation(af, al, autoAlan);
       af.alignPanel.alignmentChanged();
     }
     else
@@ -5269,7 +5397,7 @@ public class Jalview2XML
     else
     {
       cs = new AnnotationColourGradient(matchedAnnotation,
-              ColourSchemeProperty.getColourScheme(al,
+              ColourSchemeProperty.getColourScheme(af.getViewport(), al,
                       viewAnnColour.getColourScheme()),
               safeInt(viewAnnColour.getAboveThreshold()));
     }
@@ -5302,106 +5430,6 @@ public class Jalview2XML
     return cs;
   }
 
-  private void reorderAutoannotation(AlignFrame af, AlignmentI al,
-          List<JvAnnotRow> autoAlan)
-  {
-    // copy over visualization settings for autocalculated annotation in the
-    // view
-    if (al.getAlignmentAnnotation() != null)
-    {
-      /**
-       * Kludge for magic autoannotation names (see JAL-811)
-       */
-      String[] magicNames = new String[] { "Consensus", "Quality",
-          "Conservation" };
-      JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
-      Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
-      for (String nm : magicNames)
-      {
-        visan.put(nm, nullAnnot);
-      }
-      for (JvAnnotRow auan : autoAlan)
-      {
-        visan.put(auan.template.label
-                + (auan.template.getCalcId() == null ? ""
-                        : "\t" + auan.template.getCalcId()),
-                auan);
-      }
-      int hSize = al.getAlignmentAnnotation().length;
-      List<JvAnnotRow> reorder = new ArrayList<>();
-      // work through any autoCalculated annotation already on the view
-      // removing it if it should be placed in a different location on the
-      // annotation panel.
-      List<String> remains = new ArrayList<>(visan.keySet());
-      for (int h = 0; h < hSize; h++)
-      {
-        jalview.datamodel.AlignmentAnnotation jalan = al
-                .getAlignmentAnnotation()[h];
-        if (jalan.autoCalculated)
-        {
-          String k;
-          JvAnnotRow valan = visan.get(k = jalan.label);
-          if (jalan.getCalcId() != null)
-          {
-            valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
-          }
-
-          if (valan != null)
-          {
-            // delete the auto calculated row from the alignment
-            al.deleteAnnotation(jalan, false);
-            remains.remove(k);
-            hSize--;
-            h--;
-            if (valan != nullAnnot)
-            {
-              if (jalan != valan.template)
-              {
-                // newly created autoannotation row instance
-                // so keep a reference to the visible annotation row
-                // and copy over all relevant attributes
-                if (valan.template.graphHeight >= 0)
-
-                {
-                  jalan.graphHeight = valan.template.graphHeight;
-                }
-                jalan.visible = valan.template.visible;
-              }
-              reorder.add(new JvAnnotRow(valan.order, jalan));
-            }
-          }
-        }
-      }
-      // Add any (possibly stale) autocalculated rows that were not appended to
-      // the view during construction
-      for (String other : remains)
-      {
-        JvAnnotRow othera = visan.get(other);
-        if (othera != nullAnnot && othera.template.getCalcId() != null
-                && othera.template.getCalcId().length() > 0)
-        {
-          reorder.add(othera);
-        }
-      }
-      // now put the automatic annotation in its correct place
-      int s = 0, srt[] = new int[reorder.size()];
-      JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
-      for (JvAnnotRow jvar : reorder)
-      {
-        rws[s] = jvar;
-        srt[s++] = jvar.order;
-      }
-      reorder.clear();
-      jalview.util.QuickSort.sort(srt, rws);
-      // and re-insert the annotation at its correct position
-      for (JvAnnotRow jvar : rws)
-      {
-        al.addAnnotation(jvar.template, jvar.order);
-      }
-      af.alignPanel.adjustAnnotationHeight();
-    }
-  }
-
   Hashtable skipList = null;
 
   /**
@@ -6562,7 +6590,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();