Merge branch 'features/JAL-2446NCList' into features/JAL-2526sequenceCursor
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 5 Jun 2017 07:54:11 +0000 (08:54 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 5 Jun 2017 07:54:11 +0000 (08:54 +0100)
features/JAL-2526sequenceCursor

Conflicts:
src/jalview/datamodel/SequenceI.java

15 files changed:
help/html/menus/popupMenu.html
help/html/releases.html
help/html/whatsNew.html
src/jalview/datamodel/HiddenColumns.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/ensembl/EnsemblRestClient.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/PopupMenu.java
src/jalview/viewmodel/ViewportRanges.java
src/jalview/workers/ColumnCounterSetWorker.java
test/jalview/datamodel/HiddenColumnsTest.java
test/jalview/datamodel/SequenceTest.java
test/jalview/viewmodel/ViewportRangesTest.java

index d42f854..7625606 100755 (executable)
         href="../features/varna.html">VARNA</a>.
     </em></li>
     <li><a name="hideinserts"><strong>Hide Insertions</strong></a><br />
-      <em>Hides columns containing gaps in the current sequence or
-        selected region, and reveals columns not including gaps.</em>
+      <em>Hides columns containing gaps in both the current
+        sequence and selected region, and reveals columns not including
+        gaps. (before 2.10.2, this option hid or revealed columns
+        according to gaps in just the current sequence)</em></li>
     <li><strong>Hide Sequences</strong><br> <em>Hides the
         currently selected sequences in this alignment view.</em><strong><br>
     </strong></li>
index 1ac87af..cb46024 100755 (executable)
@@ -184,6 +184,7 @@ li:before {
             </li>
             <li><!-- JAL-1256 -->Trackpad horizontal scroll gesture adjusts start position in wrap mode</li>
             <li><!-- JAL-2563 -->Status bar doesn't show positions for ambiguous amino acids</li>
+            <li><!-- JAL-2291 -->Hide insertions in PopUp menu excludes gaps in selection, current sequence and only within selected columns</li> 
           </ul>
           <em>Applet</em>
           <ul>
index 3f949f8..4b82179 100755 (executable)
         web pages for database cross-references</a> via the UK Elixir's
       EMBL-EBI's MIRIAM database and identifiers.org services.
     </li>
+    <li><em>Showing and hiding regions</em>
+      <ul>
+        <li><a href="menus/popupMenu.html#hideinserts">Hide
+            insertions</a> in the PopUp menu has changed its behaviour.
+          Prior to 2.10.2, columns were only shown or hidden according
+          to gaps in the sequence under the popup menu. Now, only
+          columns that are gapped in all selected sequences as well as
+          the sequence under the popup menu are hidden, and column
+          visibility outside the selected region is left as is. This
+          makes it easy to filter insertions from the alignment view
+          (just select the region containing insertions to remove)
+          without affecting the rest of the hidden columns.</li>
+      </ul></li>
   </ul>
   <p>
     <strong><a name="experimental">Experimental Features</a></strong>
index 3685ab0..f0d99e5 100644 (file)
@@ -4,6 +4,7 @@ import jalview.util.Comparison;
 import jalview.util.ShiftList;
 
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 import java.util.Vector;
@@ -1262,4 +1263,37 @@ public class HiddenColumns
     return hashCode;
   }
 
+  /**
+   * Hide columns corresponding to the marked bits
+   * 
+   * @param inserts
+   *          - columns map to bits starting from zero
+   */
+  public void hideMarkedBits(BitSet inserts)
+  {
+    for (int firstSet = inserts.nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
+            .nextSetBit(lastSet))
+    {
+      lastSet = inserts.nextClearBit(firstSet);
+      hideColumns(firstSet, lastSet - 1);
+    }
+  }
+
+  /**
+   * 
+   * @param inserts
+   *          BitSet where hidden columns will be marked
+   */
+  public void markHiddenRegions(BitSet inserts)
+  {
+    if (hiddenColumns == null)
+    {
+      return;
+    }
+    for (int[] range : hiddenColumns)
+    {
+      inserts.set(range[0], range[1] + 1);
+    }
+  }
+
 }
index 24f904c..ab6639a 100755 (executable)
@@ -31,6 +31,7 @@ import jalview.util.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
@@ -1165,6 +1166,40 @@ public class Sequence extends ASequence implements SequenceI
   }
 
   @Override
+  public BitSet getInsertionsAsBits()
+  {
+    BitSet map = new BitSet();
+    int lastj = -1, j = 0;
+    int pos = start;
+    int seqlen = sequence.length;
+    while ((j < seqlen))
+    {
+      if (jalview.util.Comparison.isGap(sequence[j]))
+      {
+        if (lastj == -1)
+        {
+          lastj = j;
+        }
+      }
+      else
+      {
+        if (lastj != -1)
+        {
+          map.set(lastj, j);
+          lastj = -1;
+        }
+      }
+      j++;
+    }
+    if (lastj != -1)
+    {
+      map.set(lastj, j);
+      lastj = -1;
+    }
+    return map;
+  }
+
+  @Override
   public void deleteChars(int i, int j)
   {
     int newstart = start, newend = end;
index 18f0948..38be37f 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.datamodel;
 
 import jalview.datamodel.features.SequenceFeaturesI;
 
+import java.util.BitSet;
 import java.util.List;
 import java.util.Vector;
 
@@ -527,4 +528,11 @@ public interface SequenceI extends ASequenceI
    * positions to be invalidated.
    */
   void sequenceChanged();
+  
+  /**
+   * 
+   * @return BitSet corresponding to index [0,length) where Comparison.isGap()
+   *         returns true.
+   */
+  BitSet getInsertionsAsBits();
 }
index 2d4d61a..915fa0a 100644 (file)
@@ -191,7 +191,22 @@ public class EnsemblGene extends EnsemblSeqProxy
           geneIds.add(geneId);
         }
       }
+      else if (isProteinIdentifier(acc))
+      {
+        String tscriptId = new EnsemblLookup(getDomain()).getParent(acc);
+        if (tscriptId != null)
+        {
+          String geneId = new EnsemblLookup(getDomain())
+                  .getParent(tscriptId);
 
+          if (geneId != null && !geneIds.contains(geneId))
+          {
+            geneIds.add(geneId);
+          }
+        }
+        // NOTE - acc is lost if it resembles an ENS.+ ID but isn't actually
+        // resolving to one... e.g. ENSMICP00000009241
+      }
       /*
        * if given a gene or other external name, lookup and fetch 
        * the corresponding gene for all model organisms 
index 5960f81..2437588 100644 (file)
@@ -76,6 +76,9 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
 
   private final static long VERSION_RETEST_INTERVAL = 1000L * 3600; // 1 hr
 
+  private static final Regex PROTEIN_REGEX = new Regex(
+          "(ENS)([A-Z]{3}|)P[0-9]{11}$");
+
   private static final Regex TRANSCRIPT_REGEX = new Regex(
           "(ENS)([A-Z]{3}|)T[0-9]{11}$");
 
@@ -125,6 +128,18 @@ abstract class EnsemblRestClient extends EnsemblSequenceFetcher
 
   /**
    * Answers true if the query matches the regular expression pattern for an
+   * Ensembl protein stable identifier
+   * 
+   * @param query
+   * @return
+   */
+  public boolean isProteinIdentifier(String query)
+  {
+    return query == null ? false : PROTEIN_REGEX.search(query);
+  }
+
+  /**
+   * Answers true if the query matches the regular expression pattern for an
    * Ensembl gene stable identifier
    * 
    * @param query
index a15f605..9c2a1b9 100644 (file)
@@ -112,7 +112,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
     setOldHiddenColumns(av.getAlignment().getHiddenColumns());
     adjusting = true;
 
-    setAnnotations(new JComboBox<String>(getAnnotationItems(false)));
+    setAnnotations(new JComboBox<>(getAnnotationItems(false)));
     populateThresholdComboBox(threshold);
     AnnotationColumnChooser lastChooser = av
             .getAnnotationColumnSelectionState();
@@ -386,10 +386,13 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
       }
     }
 
+    // show hidden columns here, before changing the column selection in
+    // filterAnnotations, because showing hidden columns has the side effect of
+    // adding them to the selection
+    av.showAllHiddenColumns();
     av.getColumnSelection().filterAnnotations(
             getCurrentAnnotation().annotations, filterParams);
 
-    av.showAllHiddenColumns();
     if (getActionOption() == ACTION_OPTION_HIDE)
     {
       av.hideSelectedColumns();
index c27aeee..a8e2f7e 100644 (file)
@@ -57,6 +57,7 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Hashtable;
@@ -1444,24 +1445,59 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
   {
-    if (sequence != null)
+
+    HiddenColumns hidden = new HiddenColumns();
+    BitSet inserts = new BitSet(), mask = new BitSet();
+
+    // set mask to preserve existing hidden columns outside selected group
+    if (ap.av.hasHiddenColumns())
+    {
+      ap.av.getAlignment().getHiddenColumns().markHiddenRegions(mask);
+    }
+
+    boolean markedPopup = false;
+    // mark inserts in current selection
+    if (ap.av.getSelectionGroup() != null)
     {
-      /* ColumnSelection cs = ap.av.getColumnSelection();
-       if (cs == null)
-       {
-         cs = new ColumnSelection();
-       }
-       cs.hideInsertionsFor(sequence);
-       ap.av.setColumnSelection(cs);*/
+      // mark just the columns in the selection group to be hidden
+      inserts.set(ap.av.getSelectionGroup().getStartRes(), ap.av
+              .getSelectionGroup().getEndRes() + 1);
+
+      // and clear that part of the mask
+      mask.andNot(inserts);
 
-      HiddenColumns hidden = ap.av.getAlignment().getHiddenColumns();
-      if (hidden == null)
+      // now clear columns without gaps
+      for (SequenceI sq : ap.av.getSelectionGroup().getSequences())
       {
-        hidden = new HiddenColumns();
+        if (sq == sequence)
+        {
+          markedPopup = true;
+        }
+        inserts.and(sq.getInsertionsAsBits());
       }
-      hidden.hideInsertionsFor(sequence);
-      ap.av.getAlignment().setHiddenColumns(hidden);
     }
+    else
+    {
+      // initially, mark all columns to be hidden
+      inserts.set(0, ap.av.getAlignment().getWidth());
+
+      // and clear out old hidden regions completely
+      mask.clear();
+    }
+
+    // now mark for sequence under popup if we haven't already done it
+    if (!markedPopup && sequence != null)
+    {
+      inserts.and(sequence.getInsertionsAsBits());
+    }
+
+    // finally, preserve hidden regions outside selection
+    inserts.or(mask);
+
+    // and set hidden columns accordingly
+    hidden.hideMarkedBits(inserts);
+
+    ap.av.getAlignment().setHiddenColumns(hidden);
     refresh();
   }
 
index 743d212..4eb8c95 100644 (file)
@@ -123,7 +123,7 @@ public class ViewportRanges extends ViewportProperties
     int oldstartres = this.startRes;
     if (start > getVisibleAlignmentWidth() - 1)
     {
-      startRes = getVisibleAlignmentWidth() - 1;
+      startRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
     }
     else if (start < 0)
     {
@@ -141,7 +141,7 @@ public class ViewportRanges extends ViewportProperties
     }
     else if (end > getVisibleAlignmentWidth() - 1)
     {
-      endRes = getVisibleAlignmentWidth() - 1;
+      endRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
     }
     else
     {
@@ -208,7 +208,7 @@ public class ViewportRanges extends ViewportProperties
     int oldstartseq = this.startSeq;
     if (start > getVisibleAlignmentHeight() - 1)
     {
-      startSeq = getVisibleAlignmentHeight() - 1;
+      startSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
     }
     else if (start < 0)
     {
@@ -222,7 +222,7 @@ public class ViewportRanges extends ViewportProperties
     int oldendseq = this.endSeq;
     if (end >= getVisibleAlignmentHeight())
     {
-      endSeq = getVisibleAlignmentHeight() - 1;
+      endSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
     }
     else if (end < 0)
     {
index 6c5707d..2422748 100644 (file)
@@ -182,19 +182,19 @@ class ColumnCounterSetWorker extends AlignCalcWorker
     for (int anrow = 0; anrow < rows; anrow++)
     {
       Annotation[] anns = new Annotation[width];
+      long rmax = 0;
       /*
-       * add non-zero counts as annotations
+       * add counts as annotations. zeros are needed since select-by-annotation ignores empty annotation positions
        */
       for (int i = 0; i < counts.length; i++)
       {
         int count = counts[i][anrow];
-        if (count > 0)
-        {
-          Color color = ColorUtils.getGraduatedColour(count, 0, minColour,
-                  max[anrow], maxColour);
-          String str = String.valueOf(count);
-          anns[i] = new Annotation(str, str, '0', count, color);
-        }
+
+        Color color = ColorUtils.getGraduatedColour(count, 0, minColour,
+                max[anrow], maxColour);
+        String str = String.valueOf(count);
+        anns[i] = new Annotation(str, str, '0', count, color);
+        rmax = Math.max(count, rmax);
       }
 
       /*
@@ -212,7 +212,8 @@ class ColumnCounterSetWorker extends AlignCalcWorker
       ann.scaleColLabel = true;
       ann.graph = AlignmentAnnotation.BAR_GRAPH;
       ann.annotations = anns;
-      setGraphMinMax(ann, anns);
+      ann.graphMin = 0f; // minimum always zero count
+      ann.graphMax = rmax; // maximum count from loop over feature columns
       ann.validateRangeAndDisplay();
       if (!ourAnnots.contains(ann))
       {
index b767cf7..10808d6 100644 (file)
@@ -29,7 +29,9 @@ import jalview.analysis.AlignmentGenerator;
 import jalview.gui.JvOptionPane;
 
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.List;
+import java.util.Random;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -517,4 +519,70 @@ public class HiddenColumnsTest
     assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
   }
 
+  @Test(groups = { "Functional" })
+  public void testHideBitset()
+  {
+    HiddenColumns cs;
+
+    BitSet one = new BitSet();
+
+    // one hidden range
+    one.set(1);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+    assertEquals(1, cs.getHiddenRegions().size());
+
+    one.set(2);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+    assertEquals(1, cs.getHiddenRegions().size());
+
+    one.set(3);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+    assertEquals(1, cs.getHiddenRegions().size());
+
+    // split
+    one.clear(2);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+    assertEquals(2, cs.getHiddenRegions().size());
+
+    assertEquals(0, cs.adjustForHiddenColumns(0));
+    assertEquals(2, cs.adjustForHiddenColumns(1));
+    assertEquals(4, cs.adjustForHiddenColumns(2));
+
+    // one again
+    one.clear(1);
+    cs = new HiddenColumns();
+    cs.hideMarkedBits(one);
+
+    assertEquals(1, cs.getHiddenRegions().size());
+
+    assertEquals(0, cs.adjustForHiddenColumns(0));
+    assertEquals(1, cs.adjustForHiddenColumns(1));
+    assertEquals(2, cs.adjustForHiddenColumns(2));
+    assertEquals(4, cs.adjustForHiddenColumns(3));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testGetBitset()
+  {
+    BitSet toMark, fromMark;
+    long seed = -3241532;
+    Random number = new Random(seed);
+    for (int n = 0; n < 1000; n++)
+    {
+      // create a random bitfield
+      toMark = BitSet.valueOf(new long[] { number.nextLong(),
+          number.nextLong(), number.nextLong() });
+      toMark.set(n * number.nextInt(10), n * (25 + number.nextInt(25)));
+      HiddenColumns hc = new HiddenColumns();
+      hc.hideMarkedBits(toMark);
+
+      // see if we can recover bitfield
+      hc.markHiddenRegions(fromMark = new BitSet());
+      assertEquals(toMark, fromMark);
+    }
+  }
 }
index 4f42947..c5850dc 100644 (file)
@@ -37,6 +37,7 @@ import jalview.util.MapList;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.List;
 import java.util.Vector;
 
@@ -78,6 +79,18 @@ public class SequenceTest
     assertEquals("Gap interval 1 end wrong", 4, gapInt.get(0)[1]);
     assertEquals("Gap interval 2 start wrong", 6, gapInt.get(1)[0]);
     assertEquals("Gap interval 2 end wrong", 8, gapInt.get(1)[1]);
+
+    BitSet gapfield = aseq.getInsertionsAsBits();
+    BitSet expectedgaps = new BitSet();
+    expectedgaps.set(2, 5);
+    expectedgaps.set(6, 9);
+
+    assertEquals(6, expectedgaps.cardinality());
+
+    assertEquals("getInsertionsAsBits didn't mark expected number of gaps",
+            6, gapfield.cardinality());
+
+    assertEquals("getInsertionsAsBits not correct.", expectedgaps, gapfield);
   }
 
   @Test(groups = ("Functional"))
index 80bd4db..636f8dd 100644 (file)
@@ -25,12 +25,14 @@ public class ViewportRangesTest {
 
   AlignmentI smallAl = gen.generate(7, 2, 2, 5, 5);
 
-  @BeforeMethod
+  @BeforeMethod(alwaysRun = true)
   public void cleanUp()
   {
     ColumnSelection sel = new ColumnSelection();
     al.getHiddenColumns().revealAllHiddenColumns(sel);
     al.getHiddenSequences().showAll(null);
+    smallAl.getHiddenColumns().revealAllHiddenColumns(sel);
+    smallAl.getHiddenSequences().showAll(null);
   }
 
   @Test(groups = { "Functional" })
@@ -133,6 +135,12 @@ public class ViewportRangesTest {
     ViewportRanges vrsmall = new ViewportRanges(smallAl);
     vrsmall.setStartEndRes(al.getWidth(), al.getWidth());
     assertEquals(vrsmall.getEndRes(), 6);
+
+    // make visible alignment width = 0
+    smallAl.getHiddenColumns().hideColumns(0, 6);
+    vrsmall.setStartEndRes(0, 4);
+    assertEquals(vrsmall.getStartRes(), 0);
+    assertEquals(vrsmall.getEndRes(), 0);
   }
 
   @Test(groups = { "Functional" })
@@ -149,6 +157,14 @@ public class ViewportRangesTest {
 
     vr.setStartEndSeq(al.getHeight(), al.getHeight());
     assertEquals(vr.getEndSeq(), al.getHeight() - 1);
+
+    // make visible alignment height = 0
+    smallAl.getHiddenSequences().hideSequence(smallAl.getSequenceAt(0));
+    smallAl.getHiddenSequences().hideSequence(smallAl.getSequenceAt(0));
+    ViewportRanges vrsmall = new ViewportRanges(smallAl);
+    vrsmall.setStartEndSeq(0, 3);
+    assertEquals(vrsmall.getStartSeq(), 0);
+    assertEquals(vrsmall.getEndSeq(), 0);
   }
 
   @Test(groups = { "Functional" })