Merge branch 'features/JAL-4034_improve_3dbeacons_button_and_ux' into develop
[jalview.git] / src / jalview / gui / AppVarna.java
index 9316b05..3a64716 100644 (file)
  */
 package jalview.gui;
 
+import jalview.analysis.AlignSeq;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.RnaViewerModel;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.ext.varna.RnaModel;
+import jalview.structure.SecondaryStructureListener;
+import jalview.structure.SelectionListener;
+import jalview.structure.SelectionSource;
+import jalview.structure.StructureSelectionManager;
+import jalview.structure.VamsasSource;
+import jalview.util.Comparison;
+import jalview.util.MessageManager;
+import jalview.util.ShiftList;
+
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.util.Collection;
@@ -27,8 +44,6 @@ import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import javax.swing.JInternalFrame;
 import javax.swing.JSplitPane;
@@ -46,28 +61,12 @@ import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation;
 import fr.orsay.lri.varna.models.rna.ModeleBase;
 import fr.orsay.lri.varna.models.rna.RNA;
 
-import jalview.analysis.AlignSeq;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.RnaViewerModel;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.ext.varna.RnaModel;
-import jalview.structure.SecondaryStructureListener;
-import jalview.structure.SelectionListener;
-import jalview.structure.SelectionSource;
-import jalview.structure.StructureSelectionManager;
-import jalview.structure.VamsasSource;
-import jalview.util.Comparison;
-import jalview.util.MessageManager;
-import jalview.util.ShiftList;
-
-public class AppVarna extends JInternalFrame implements SelectionListener,
-        SecondaryStructureListener, InterfaceVARNASelectionListener,
-        VamsasSource
+public class AppVarna extends JInternalFrame
+        implements SelectionListener, SecondaryStructureListener,
+        InterfaceVARNASelectionListener, VamsasSource
 {
-  private static final Pattern PAIRS_PATTERN = Pattern
-          .compile("[^([{<>}])]");
+  private static final byte[] PAIRS = new byte[] { '(', ')', '[', ']', '{',
+      '}', '<', '>' };
 
   private AppVarnaBinding vab;
 
@@ -121,6 +120,15 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
       }
     }
 
+    /**
+     * highlight a region from start to end (inclusive) on rna
+     * 
+     * @param rna
+     * @param start
+     *          - first base pair index (from 0)
+     * @param end
+     *          - last base pair index (from 0)
+     */
     public void highlightRegion(RNA rna, int start, int end)
     {
       clearLastSelection();
@@ -167,8 +175,6 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
   /**
    * Constructor
    * 
-   * @param sname
-   *          a descriptive name
    * @param seq
    *          the RNA sequence
    * @param aa
@@ -176,50 +182,28 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
    * @param ap
    *          the AlignmentPanel creating this object
    */
-  public AppVarna(String sname, SequenceI seq, AlignmentAnnotation aa,
-          AlignmentPanel ap)
+  public AppVarna(SequenceI seq, AlignmentAnnotation aa, AlignmentPanel ap)
   {
     this(ap);
 
-    String name = sname + " trimmed to " + seq.getName();
-    String fullName = MessageManager.formatMessage("label.varna_params",
+    String sname = aa.sequenceRef == null
+            ? "secondary structure (alignment)"
+            : seq.getName() + " structure";
+    String theTitle = sname
+            + (aa.sequenceRef == null ? " trimmed to " + seq.getName()
+                    : "");
+    theTitle = MessageManager.formatMessage("label.varna_params",
             new String[]
-            { name });
-    setTitle(fullName);
-
-    /*
-     * if (!aa.isValidStruc()) { throw new
-     * IllegalArgumentException("Invalid RNA structure annotation"); } final
-     * String struc = aa.getRNAStruc();
-     * 
-     * String strucseq = seq.getSequenceAsString();
-     * 
-     * String gappedTitle = sname + " (with gaps)"; String rnaTitle =
-     * gappedTitle; RNA gapped = new RNA(rnaTitle); try {
-     * gapped.setRNA(strucseq, replaceOddGaps(struc)); } catch
-     * (ExceptionUnmatchedClosingParentheses e2) { e2.printStackTrace(); } catch
-     * (ExceptionFileFormatOrSyntax e3) { e3.printStackTrace(); }
-     * models.put(gapped, new RnaModel(rnaTitle, aa, seq, gapped, true, null));
-     * 
-     * String trimmedTitle = "trimmed " + sname; rnaTitle = trimmedTitle; RNA
-     * trimmed = trimRNA(gapped, rnaTitle); models.put(trimmed, new
-     * RnaModel(rnaTitle, aa, seq, trimmed, false, null));
-     */
-    // vab = new AppVarnaBinding(Arrays.asList(new RNA[]
-    // { trimmed, gapped }));
-    // vab = new AppVarnaBinding();
-    // // String seqName = seq.getName();
-    // // String name = sname + " trimmed to " + seqName;
-    // initVarna(name);
+            { theTitle });
+    setTitle(theTitle);
 
     String gappedTitle = sname + " (with gaps)";
-    RnaModel gappedModel = new RnaModel(gappedTitle, aa, seq, null, true,
-            null);
+    RnaModel gappedModel = new RnaModel(gappedTitle, aa, seq, null, true);
     addModel(gappedModel, gappedTitle);
 
     String trimmedTitle = "trimmed " + sname;
     RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null,
-            false, null);
+            false);
     addModel(trimmedModel, trimmedTitle);
     vab.setSelectedIndex(0);
   }
@@ -286,13 +270,6 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
     showPanel(true);
   }
 
-  public String replaceOddGaps(String oldStr)
-  {
-    Matcher matcher = PAIRS_PATTERN.matcher(oldStr);
-    String newStr = matcher.replaceAll(".");
-    return newStr;
-  }
-
   /**
    * Constructs a new RNA model from the given one, without gaps. Also
    * calculates and saves a 'shift list'
@@ -308,7 +285,8 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
     RNA rnaTrim = new RNA(name);
     try
     {
-      rnaTrim.setRNA(rna.getSeq(), replaceOddGaps(rna.getStructDBN()));
+      String structDBN = rna.getStructDBN(true);
+      rnaTrim.setRNA(rna.getSeq(), replaceOddGaps(structDBN));
     } catch (ExceptionUnmatchedClosingParentheses e2)
     {
       e2.printStackTrace();
@@ -319,7 +297,7 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
 
     String seq = rnaTrim.getSeq();
     StringBuilder struc = new StringBuilder(256);
-    struc.append(rnaTrim.getStructDBN());
+    struc.append(rnaTrim.getStructDBN(true));
     int ofstart = -1;
     int sleng = seq.length();
 
@@ -332,7 +310,7 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
           ofstart = i;
         }
         /*
-         * mark base or base pair in the structure with *
+         * mark base or base & pair in the structure with *
          */
         if (!rnaTrim.findPair(i).isEmpty())
         {
@@ -409,32 +387,35 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
    * selected RNA in the VARNA window is highlighted at the specific position.
    * To be able to remove it before the next highlight it is saved in
    * _lastHighlight
+   * 
+   * @param sequence
+   * @param index
+   *          the aligned sequence position (base 0)
+   * @param position
+   *          the dataset sequence position (base 1)
    */
   @Override
-  public void mouseOverSequence(SequenceI sequence, int index)
+  public void mouseOverSequence(SequenceI sequence, final int index,
+          final int position)
   {
     RNA rna = vab.getSelectedRNA();
     if (rna == null)
     {
       return;
     }
-    if (models.get(rna).seq == sequence)
+    RnaModel rnaModel = models.get(rna);
+    if (rnaModel.seq == sequence)
     {
-      ShiftList shift = offsets.get(rna);
-      if (shift != null)
-      {
-        // System.err.print("Orig pos:"+index);
-        index = shift.shift(index);
-        // System.err.println("\nFinal pos:"+index);
-      }
-      mouseOverHighlighter.highlightRegion(rna, index, index);
+      int highlightPos = rnaModel.gapped ? index
+              : position - sequence.getStart();
+      mouseOverHighlighter.highlightRegion(rna, highlightPos, highlightPos);
       vab.updateSelectedRNA(rna);
     }
   }
 
   @Override
   public void selection(SequenceGroup seqsel, ColumnSelection colsel,
-          SelectionSource source)
+          HiddenColumns hidden, SelectionSource source)
   {
     if (source != ap.av)
     {
@@ -447,18 +428,31 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
     {
       return;
     }
-    if (seqsel != null && seqsel.getSize() > 0)
+
+    RnaModel rnaModel = models.get(rna);
+
+    if (seqsel != null && seqsel.getSize() > 0
+            && seqsel.contains(rnaModel.seq))
     {
       int start = seqsel.getStartRes(), end = seqsel.getEndRes();
-      ShiftList shift = offsets.get(rna);
-      if (shift != null)
+      if (rnaModel.gapped)
+      {
+        ShiftList shift = offsets.get(rna);
+        if (shift != null)
+        {
+          start = shift.shift(start);
+          end = shift.shift(end);
+        }
+      }
+      else
       {
-        start = shift.shift(start);
-        end = shift.shift(end);
+        start = rnaModel.seq.findPosition(start) - rnaModel.seq.getStart();
+        end = rnaModel.seq.findPosition(end) - rnaModel.seq.getStart();
       }
+
       selectionHighlighter.highlightRegion(rna, start, end);
-      selectionHighlighter.getLastHighlight().setOutlineColor(
-              seqsel.getOutlineColour());
+      selectionHighlighter.getLastHighlight()
+              .setOutlineColor(seqsel.getOutlineColour());
       // TODO - translate column markings to positions on structure if present.
       vab.updateSelectedRNA(rna);
     }
@@ -493,7 +487,8 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
   }
 
   @Override
-  public void onSelectionChanged(BaseList arg0, BaseList arg1, BaseList arg2)
+  public void onSelectionChanged(BaseList arg0, BaseList arg1,
+          BaseList arg2)
   {
     // TODO translate selected regions in VARNA to a selection on the
     // alignpanel.
@@ -610,36 +605,8 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
   {
     if (!model.ann.isValidStruc())
     {
-      throw new IllegalArgumentException("Invalid RNA structure annotation");
-    }
-
-    /*
-     * loaded from project file with Varna session data
-     */
-    if (model.varnaSession != null)
-    {
-      try
-      {
-        FullBackup fromSession = vab.vp.loadSession(model.varnaSession);
-        vab.addStructure(fromSession.rna, fromSession.config);
-        RNA rna = fromSession.rna;
-        // copy the model, but now including the RNA object
-        RnaModel newModel = new RnaModel(model.title, model.ann, model.seq,
-                rna, model.gapped, model.varnaSession);
-        if (!model.gapped)
-        {
-          registerOffset(rna, buildOffset(model.seq));
-        }
-        // TODO and add mapping (offsets)
-        models.put(rna, newModel);
-        // capture rna selection state when saved
-        selectionHighlighter = new VarnaHighlighter(rna);
-        return fromSession.rna;
-      } catch (ExceptionLoadingFailed e)
-      {
-        e.printStackTrace();
-        return null;
-      }
+      throw new IllegalArgumentException(
+              "Invalid RNA structure annotation");
     }
 
     /*
@@ -666,7 +633,7 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
       rna = trimRNA(rna, modelName);
     }
     models.put(rna, new RnaModel(modelName, model.ann, model.seq, rna,
-            model.gapped, null));
+            model.gapped));
     vab.addStructure(rna);
     return rna;
   }
@@ -680,14 +647,14 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
   protected ShiftList buildOffset(SequenceI seq)
   {
     // TODO refactor to avoid duplication with trimRNA()
+    // TODO JAL-1789 bugs in use of ShiftList here
     ShiftList offset = new ShiftList();
     int ofstart = -1;
     int sleng = seq.getLength();
-    char[] seqChars = seq.getSequence();
 
     for (int i = 0; i < sleng; i++)
     {
-      if (Comparison.isGap(seqChars[i]))
+      if (Comparison.isGap(seq.getCharAt(i)))
       {
         if (ofstart == -1)
         {
@@ -715,10 +682,101 @@ public class AppVarna extends JInternalFrame implements SelectionListener,
   /**
    * Set the selected index in the model selection list
    * 
-   * @param selectedRna
+   * @param selectedIndex
+   */
+  public void setInitialSelection(final int selectedIndex)
+  {
+    /*
+     * empirically it needs a second for Varna/AWT to finish loading/drawing
+     * models for this to work; SwingUtilities.invokeLater _not_ a solution;
+     * explanation and/or better solution welcome!
+     */
+    synchronized (this)
+    {
+      try
+      {
+        wait(1000);
+      } catch (InterruptedException e)
+      {
+        // meh
+      }
+    }
+    vab.setSelectedIndex(selectedIndex);
+  }
+
+  /**
+   * Add a model with associated Varna session file
+   * 
+   * @param rna
+   * @param modelName
+   */
+  public RNA addModelSession(RnaModel model, String modelName,
+          String sessionFile)
+  {
+    if (!model.ann.isValidStruc())
+    {
+      throw new IllegalArgumentException(
+              "Invalid RNA structure annotation");
+    }
+
+    try
+    {
+      FullBackup fromSession = vab.vp.loadSession(sessionFile);
+      vab.addStructure(fromSession.rna, fromSession.config);
+      RNA rna = fromSession.rna;
+      // copy the model, but now including the RNA object
+      RnaModel newModel = new RnaModel(model.title, model.ann, model.seq,
+              rna, model.gapped);
+      if (!model.gapped)
+      {
+        registerOffset(rna, buildOffset(model.seq));
+      }
+      models.put(rna, newModel);
+      // capture rna selection state when saved
+      selectionHighlighter = new VarnaHighlighter(rna);
+      return fromSession.rna;
+    } catch (ExceptionLoadingFailed e)
+    {
+      System.err
+              .println("Error restoring Varna session: " + e.getMessage());
+      return null;
+    }
+  }
+
+  /**
+   * Replace everything except RNA secondary structure characters with a period
+   * 
+   * @param s
+   * @return
    */
-  public void setSelectedIndex(int selectedRna)
+  public static String replaceOddGaps(String s)
   {
-    vab.setSelectedIndex(selectedRna);
+    if (s == null)
+    {
+      return null;
+    }
+
+    // this is measured to be 10 times faster than a regex replace
+    boolean changed = false;
+    byte[] bytes = s.getBytes();
+    for (int i = 0; i < bytes.length; i++)
+    {
+      boolean ok = false;
+      // todo check for ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) if
+      // wanted also
+      for (int j = 0; !ok && (j < PAIRS.length); j++)
+      {
+        if (bytes[i] == PAIRS[j])
+        {
+          ok = true;
+        }
+      }
+      if (!ok)
+      {
+        bytes[i] = '.';
+        changed = true;
+      }
+    }
+    return changed ? new String(bytes) : s;
   }
 }