Merge branch 'releases/Release_2_10_4_Branch_merged_JAL-2759' into releases/Release_2...
authorJim Procter <jprocter@issues.jalview.org>
Wed, 28 Feb 2018 10:32:14 +0000 (10:32 +0000)
committerJim Procter <jprocter@issues.jalview.org>
Wed, 28 Feb 2018 10:32:14 +0000 (10:32 +0000)
66 files changed:
benchmarking/README
benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java [new file with mode: 0644]
benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java
help/html/releases.html
src/jalview/analysis/Dna.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/appletgui/IdCanvas.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/bin/JalviewLite.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/AlignmentI.java
src/jalview/datamodel/CigarArray.java
src/jalview/datamodel/HiddenColumns.java
src/jalview/datamodel/HiddenColumnsCursor.java [new file with mode: 0644]
src/jalview/datamodel/HiddenCursorPosition.java [new file with mode: 0644]
src/jalview/datamodel/RangeElementsIterator.java [new file with mode: 0644]
src/jalview/datamodel/RangeIterator.java [new file with mode: 0644]
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/StartRegionIterator.java [new file with mode: 0644]
src/jalview/datamodel/VisibleColsCollection.java
src/jalview/datamodel/VisibleColsIterator.java [deleted file]
src/jalview/datamodel/VisibleContigsIterator.java [new file with mode: 0644]
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/VamsasApplication.java
src/jalview/io/AnnotationFile.java
src/jalview/io/FormatAdapter.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/renderer/ScaleRenderer.java
src/jalview/util/MappingUtils.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/OverviewDimensionsHideHidden.java
src/jalview/viewmodel/OverviewDimensionsShowHidden.java
src/jalview/viewmodel/ViewportRanges.java
src/jalview/ws/jws1/JPredThread.java
test/jalview/analysis/DnaTest.java
test/jalview/datamodel/AlignmentAnnotationTests.java
test/jalview/datamodel/AlignmentTest.java
test/jalview/datamodel/ColumnSelectionTest.java
test/jalview/datamodel/HiddenColumnsCursorTest.java [new file with mode: 0644]
test/jalview/datamodel/HiddenColumnsTest.java
test/jalview/datamodel/RangeElementsIteratorTest.java [moved from test/jalview/datamodel/VisibleColsIteratorTest.java with 84% similarity]
test/jalview/datamodel/SequenceTest.java
test/jalview/datamodel/StartRegionIteratorTest.java [new file with mode: 0644]
test/jalview/datamodel/VisibleContigsIteratorTest.java [new file with mode: 0644]
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/AnnotationColumnChooserTest.java
test/jalview/gui/PopupMenuTest.java
test/jalview/io/JSONFileTest.java
test/jalview/util/MappingUtilsTest.java

index ccf53c1..d1ec146 100644 (file)
@@ -27,6 +27,9 @@ to install the jalview.jar file in the local maven repository. The pom.xml in th
   To get JSON output instead use:
   java -jar target/benchmarks.jar -rf json
   
+  To run a specific benchmark file use:
+  java -jar target/benchmarks.jar <Benchmark class name> 
+  
   JSON output can be viewed quickly by drag-dropping on http://jmh.morethan.io/
   
   To get help use the standard -h option:
@@ -38,3 +41,4 @@ to install the jalview.jar file in the local maven repository. The pom.xml in th
   
   
  6. If you make changes to the Jalview code everything will need to be refreshed, by performing steps 3-5 again.
+
diff --git a/benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java b/benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java
new file mode 100644 (file)
index 0000000..477bfad
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package org.jalview;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Param;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+
+/*
+ * A class to benchmark hidden columns performance
+ */
+@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Fork(1)
+public class HiddenColsIteratorsBenchmark {
+       /*
+        * State with multiple hidden columns and a start position set
+        */
+       @State(Scope.Thread)
+       public static class HiddenColsAndStartState
+       {
+               @Param({"300", "10000", "100000"})
+               public int maxcols;
+               
+               @Param({"1", "50", "90"})
+               public int startpcnt; // position as percentage of maxcols
+               
+               @Param({"1","15","100"})
+               public int hide;
+               
+               HiddenColumns h = new HiddenColumns();
+               Random rand = new Random();
+               
+               public int hiddenColumn;
+               public int visibleColumn;
+       
+               @Setup
+               public void setup()
+               {
+                       rand.setSeed(1234);
+                       int lastcol = 0;
+               while (lastcol < maxcols)
+               {
+                       int count = rand.nextInt(100); 
+                       lastcol += count;
+                       h.hideColumns(lastcol, lastcol+hide);
+                       lastcol+=hide;
+               }
+               
+               // make sure column at start is hidden
+               hiddenColumn = (int)(maxcols * startpcnt/100.0);
+               h.hideColumns(hiddenColumn, hiddenColumn);
+               
+               // and column <hide> after start is visible
+               ColumnSelection sel = new ColumnSelection();
+               h.revealHiddenColumns(hiddenColumn+hide, sel);
+               visibleColumn = hiddenColumn+hide;
+               
+               System.out.println("Maxcols: " + maxcols + " HiddenCol: " + hiddenColumn + " Hide: " + hide);
+               System.out.println("Number of hidden columns: " + h.getSize());
+               }
+       }
+       
+       /* Convention: functions in alphabetical order */
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+       public int benchStartIterator(HiddenColsAndStartState tstate)
+       {
+               int res = 0;
+               int startx = tstate.visibleColumn;
+               Iterator<Integer> it = tstate.h.getStartRegionIterator(startx,
+                               startx+60);
+        while (it.hasNext())
+        {
+          res = it.next() - startx;
+          Blackhole.consumeCPU(5);
+        }
+        return res;
+       }
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+       public int benchBoundedIterator(HiddenColsAndStartState tstate)
+       {
+               int startx = tstate.visibleColumn;
+               int blockStart = startx;
+               int blockEnd;
+               int screenY = 0;
+               Iterator<int[]> it = tstate.h.getBoundedIterator(startx,
+                               startx+60);
+        while (it.hasNext())
+        {
+               int[] region = it.next();
+          
+               blockEnd = Math.min(region[0] - 1, blockStart + 60 - screenY);
+             
+               screenY += blockEnd - blockStart + 1;
+               blockStart = region[1] + 1;
+               
+               Blackhole.consumeCPU(5);
+        }
+        return blockStart;
+       }
+}
index d3c67d7..07b76f9 100644 (file)
@@ -47,128 +47,111 @@ import jalview.datamodel.HiddenColumns;
 @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
 @Fork(1)
 public class HiddenColumnsBenchmark 
-{
-  /*
-   * State with multiple hidden columns and a start position set
-   */
-  @State(Scope.Thread)
-  public static class HiddenColsAndStartState
-  {
-    @Param({ "300", "10000", "100000" })
-    public int maxcols;
-
-    @Param({ "1", "50", "90" })
-    public int startpcnt; // position as percentage of maxcols
-
-    @Param({ "1", "15", "100" })
-    public int hide;
-
-    HiddenColumns h = new HiddenColumns();
-
-    Random rand = new Random();
-
-    public int hiddenColumn;
-
-    public int visibleColumn;
-
-    @Setup
-    public void setup()
+{      
+       /*
+        * State with multiple hidden columns and a start position set
+        */
+       @State(Scope.Thread)
+       public static class HiddenColsAndStartState
+       {
+               @Param({"100", "1000", "10000", "100000", "1000000"})
+               public int maxcols;
+               
+               @Param({"1", "50", "90"})
+               public int startpcnt; // position as percentage of maxcols
+               
+               @Param({"1","15","100"})
+               public int hide;
+               
+               HiddenColumns h = new HiddenColumns();
+               Random rand = new Random();
+               
+               public int hiddenColumn;
+               public int visibleColumn;
+       
+               @Setup
+               public void setup()
+               {
+                       rand.setSeed(1234);
+                       int lastcol = 0;
+               while (lastcol < maxcols)
+               {
+                       int count = rand.nextInt(100); 
+                       lastcol += count;
+                       h.hideColumns(lastcol, lastcol+hide);
+                       lastcol+=hide;
+               }
+               
+               // make sure column at start is hidden
+               hiddenColumn = (int)(maxcols * startpcnt/100.0);
+               h.hideColumns(hiddenColumn, hiddenColumn);
+               
+               // and column <hide> after start is visible
+               ColumnSelection sel = new ColumnSelection();
+               h.revealHiddenColumns(hiddenColumn+hide, sel);
+               visibleColumn = hiddenColumn+hide;
+               
+               System.out.println("Maxcols: " + maxcols + " HiddenCol: " + hiddenColumn + " Hide: " + hide);
+               System.out.println("Number of hidden columns: " + h.getSize());
+               }
+       }
+       
+       /* Convention: functions in alphabetical order */
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+       public int benchAdjustForHiddenColumns(HiddenColsAndStartState tstate)
+       {
+               return tstate.h.visibleToAbsoluteColumn(tstate.visibleColumn);
+       }
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+       public int benchFindColumnPosition(HiddenColsAndStartState tstate)
+       {
+               return tstate.h.absoluteToVisibleColumn(tstate.visibleColumn);
+       }
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+    public int benchGetSize(HiddenColsAndStartState tstate)
     {
-      rand.setSeed(1234);
-      int lastcol = 0;
-      while (lastcol < maxcols)
-      {
-        int count = rand.nextInt(100);
-        lastcol += count;
-        h.hideColumns(lastcol, lastcol + hide);
-        lastcol += hide;
-      }
-
-      // make sure column at start is hidden
-      hiddenColumn = (int) (maxcols * startpcnt / 100.0);
-      h.hideColumns(hiddenColumn, hiddenColumn);
-
-      // and column <hide> after start is visible
-      ColumnSelection sel = new ColumnSelection();
-      h.revealHiddenColumns(hiddenColumn + hide, sel);
-      visibleColumn = hiddenColumn + hide;
-
-      System.out.println("Maxcols: " + maxcols + " HiddenCol: "
-              + hiddenColumn + " Hide: " + hide);
-      System.out.println("Number of hidden columns: " + h.getSize());
+               return tstate.h.getSize();
     }
-  }
-
-  /* Convention: functions in alphabetical order */
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public int benchAdjustForHiddenColumns(HiddenColsAndStartState tstate)
-  {
-    return tstate.h.adjustForHiddenColumns(tstate.visibleColumn);
-  }
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public int benchFindColumnPosition(HiddenColsAndStartState tstate)
-  {
-    return tstate.h.findColumnPosition(tstate.visibleColumn);
-  }
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public List<Integer> benchFindHiddenRegionPositions(
-          HiddenColsAndStartState tstate)
-  {
-    return tstate.h.findHiddenRegionPositions();
-  }
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public ArrayList<int[]> benchGetHiddenColumnsCopy(
-          HiddenColsAndStartState tstate)
-  {
-    return tstate.h.getHiddenColumnsCopy();
-  }
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public int benchGetSize(HiddenColsAndStartState tstate)
-  {
-    return tstate.h.getSize();
-  }
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public HiddenColumns benchHideCols(HiddenColsAndStartState tstate)
-  {
-    tstate.h.hideColumns(tstate.visibleColumn, tstate.visibleColumn + 2000);
-    return tstate.h;
-  }
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public boolean benchIsVisible(HiddenColsAndStartState tstate)
-  {
-    return tstate.h.isVisible(tstate.hiddenColumn);
-  }
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public HiddenColumns benchReveal(HiddenColsAndStartState tstate)
-  {
-    ColumnSelection sel = new ColumnSelection();
-    tstate.h.revealHiddenColumns(tstate.hiddenColumn, sel);
-    return tstate.h;
-  }
-
-  @Benchmark
-  @BenchmarkMode({ Mode.Throughput })
-  public HiddenColumns benchRevealAll(HiddenColsAndStartState tstate)
-  {
-    ColumnSelection sel = new ColumnSelection();
-    tstate.h.revealAllHiddenColumns(sel);
-    return tstate.h;
-  }
 
+   @Benchmark
+    @BenchmarkMode({Mode.Throughput})
+    public HiddenColumns benchHideCols(HiddenColsAndStartState tstate) 
+    {
+       tstate.h.hideColumns(tstate.visibleColumn,
+                       tstate.visibleColumn+2000); 
+       return tstate.h;
+    }
+   
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+    public boolean benchIsVisible(HiddenColsAndStartState tstate) 
+    {
+       return tstate.h.isVisible(tstate.hiddenColumn); 
+    }
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+    public HiddenColumns benchReveal(HiddenColsAndStartState tstate) 
+    {
+               ColumnSelection sel = new ColumnSelection();
+       tstate.h.revealHiddenColumns(tstate.hiddenColumn, sel);
+       return tstate.h;
+    }
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+    public HiddenColumns benchRevealAll(HiddenColsAndStartState tstate) 
+    {
+               ColumnSelection sel = new ColumnSelection();
+       tstate.h.revealAllHiddenColumns(sel);
+       return tstate.h;
+    }
+       
+       
 }
\ No newline at end of file
index 6f15361..68cd198 100755 (executable)
@@ -81,6 +81,9 @@ li:before {
             <li>
             <!-- JAL-2885 -->Jalview uses HTTPS for Uniprot, Xfam and Ensembl services 
             </li>
+            <li>
+            <!-- JAL-2759 -->Improved performance for large alignments and lots of hidden columns
+            </li>
           </ul>
           </div>
       </td>
index a10b037..d534c8f 100644 (file)
@@ -44,6 +44,7 @@ import jalview.util.ShiftList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
 
 public class Dna
@@ -56,19 +57,23 @@ public class Dna
    * 'final' variables describe the inputs to the translation, which should not
    * be modified.
    */
-  final private List<SequenceI> selection;
+  private final List<SequenceI> selection;
 
-  final private String[] seqstring;
+  private final String[] seqstring;
 
-  final private int[] contigs;
+  private final Iterator<int[]> contigs;
 
-  final private char gapChar;
+  private final char gapChar;
 
-  final private AlignmentAnnotation[] annotations;
+  private final AlignmentAnnotation[] annotations;
 
-  final private int dnaWidth;
+  private final int dnaWidth;
 
-  final private AlignmentI dataset;
+  private final AlignmentI dataset;
+
+  private ShiftList vismapping;
+
+  private int[] startcontigs;
 
   /*
    * Working variables for the translation.
@@ -91,7 +96,7 @@ public class Dna
    * @param viewport
    * @param visibleContigs
    */
-  public Dna(AlignViewportI viewport, int[] visibleContigs)
+  public Dna(AlignViewportI viewport, Iterator<int[]> visibleContigs)
   {
     this.selection = Arrays.asList(viewport.getSequenceSelection());
     this.seqstring = viewport.getViewAsString(true);
@@ -100,6 +105,45 @@ public class Dna
     this.annotations = viewport.getAlignment().getAlignmentAnnotation();
     this.dnaWidth = viewport.getAlignment().getWidth();
     this.dataset = viewport.getAlignment().getDataset();
+    initContigs();
+  }
+
+  /**
+   * Initialise contigs used as starting point for translateCodingRegion
+   */
+  private void initContigs()
+  {
+    vismapping = new ShiftList(); // map from viscontigs to seqstring
+    // intervals
+
+    int npos = 0;
+    int[] lastregion = null;
+    ArrayList<Integer> tempcontigs = new ArrayList<>();
+    while (contigs.hasNext())
+    {
+      int[] region = contigs.next();
+      if (lastregion == null)
+      {
+        vismapping.addShift(npos, region[0]);
+      }
+      else
+      {
+        // hidden region
+        vismapping.addShift(npos, region[0] - lastregion[1] + 1);
+      }
+      lastregion = region;
+      tempcontigs.add(region[0]);
+      tempcontigs.add(region[1]);
+    }
+
+    startcontigs = new int[tempcontigs.size()];
+    int i = 0;
+    for (Integer val : tempcontigs)
+    {
+      startcontigs[i] = val;
+      i++;
+    }
+    tempcontigs = null;
   }
 
   /**
@@ -161,7 +205,7 @@ public class Dna
 
     int s;
     int sSize = selection.size();
-    List<SequenceI> pepseqs = new ArrayList<SequenceI>();
+    List<SequenceI> pepseqs = new ArrayList<>();
     for (s = 0; s < sSize; s++)
     {
       SequenceI newseq = translateCodingRegion(selection.get(s),
@@ -213,7 +257,7 @@ public class Dna
       if (dnarefs != null)
       {
         // intersect with pep
-        List<DBRefEntry> mappedrefs = new ArrayList<DBRefEntry>();
+        List<DBRefEntry> mappedrefs = new ArrayList<>();
         DBRefEntry[] refs = dna.getDBRefs();
         for (int d = 0; d < refs.length; d++)
         {
@@ -391,27 +435,13 @@ public class Dna
           String seqstring, AlignedCodonFrame acf,
           List<SequenceI> proteinSeqs)
   {
-    List<int[]> skip = new ArrayList<int[]>();
-    int skipint[] = null;
-    ShiftList vismapping = new ShiftList(); // map from viscontigs to seqstring
-    // intervals
-    int vc;
-    int[] scontigs = new int[contigs.length];
+    List<int[]> skip = new ArrayList<>();
+    int[] skipint = null;
     int npos = 0;
-    for (vc = 0; vc < contigs.length; vc += 2)
-    {
-      if (vc == 0)
-      {
-        vismapping.addShift(npos, contigs[vc]);
-      }
-      else
-      {
-        // hidden region
-        vismapping.addShift(npos, contigs[vc] - contigs[vc - 1] + 1);
-      }
-      scontigs[vc] = contigs[vc];
-      scontigs[vc + 1] = contigs[vc + 1];
-    }
+    int vc = 0;
+
+    int[] scontigs = new int[startcontigs.length];
+    System.arraycopy(startcontigs, 0, scontigs, 0, startcontigs.length);
 
     // allocate a roughly sized buffer for the protein sequence
     StringBuilder protein = new StringBuilder(seqstring.length() / 2);
@@ -800,7 +830,7 @@ public class Dna
   public AlignmentI reverseCdna(boolean complement)
   {
     int sSize = selection.size();
-    List<SequenceI> reversed = new ArrayList<SequenceI>();
+    List<SequenceI> reversed = new ArrayList<>();
     for (int s = 0; s < sSize; s++)
     {
       SequenceI newseq = reverseSequence(selection.get(s).getName(),
index ef87671..73cabc4 100644 (file)
@@ -1905,7 +1905,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   static StringBuffer copiedSequences;
 
-  static Vector<int[]> copiedHiddenColumns;
+  static HiddenColumns copiedHiddenColumns;
 
   protected void copy_actionPerformed()
   {
@@ -1929,14 +1929,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     if (viewport.hasHiddenColumns() && viewport.getSelectionGroup() != null)
     {
-      copiedHiddenColumns = new Vector<>(viewport.getAlignment()
-              .getHiddenColumns().getHiddenColumnsCopy());
       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
-      for (int[] region : copiedHiddenColumns)
-      {
-        region[0] = region[0] - hiddenOffset;
-        region[1] = region[1] - hiddenOffset;
-      }
+      int hiddenCutoff = viewport.getSelectionGroup().getEndRes();
+
+      // create new HiddenColumns object with copy of hidden regions
+      // between startRes and endRes, offset by startRes
+      copiedHiddenColumns = new HiddenColumns(
+              viewport.getAlignment().getHiddenColumns(), hiddenOffset,
+              hiddenCutoff, hiddenOffset);
     }
     else
     {
@@ -2043,14 +2043,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
         }
         AlignFrame af = new AlignFrame(new Alignment(newSeqs),
                 viewport.applet, newtitle, false);
-        if (copiedHiddenColumns != null)
-        {
-          for (int i = 0; i < copiedHiddenColumns.size(); i++)
-          {
-            int[] region = copiedHiddenColumns.elementAt(i);
-            af.viewport.hideColumns(region[0], region[1]);
-          }
-        }
+        af.viewport.setHiddenColumns(copiedHiddenColumns);
 
         jalview.bin.JalviewLite.addFrame(af, newtitle, frameWidth,
                 frameHeight);
index 270b2f7..83d8ade 100644 (file)
@@ -421,8 +421,8 @@ public class AlignmentPanel extends Panel
     if (av.hasHiddenColumns())
     {
       AlignmentI al = av.getAlignment();
-      start = al.getHiddenColumns().findColumnPosition(ostart);
-      end = al.getHiddenColumns().findColumnPosition(end);
+      start = al.getHiddenColumns().absoluteToVisibleColumn(ostart);
+      end = al.getHiddenColumns().absoluteToVisibleColumn(end);
       if (start == end)
       {
         if (!scrollToNearest && !al.getHiddenColumns().isVisible(ostart))
@@ -675,7 +675,7 @@ public class AlignmentPanel extends Panel
       if (av.hasHiddenColumns())
       {
         width = av.getAlignment().getHiddenColumns()
-                .findColumnPosition(width);
+                .absoluteToVisibleColumn(width);
       }
       if (x < 0)
       {
index d8e7007..bbcb3b8 100755 (executable)
@@ -23,6 +23,7 @@ package jalview.appletgui;
 import jalview.analysis.AlignmentUtils;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.util.MessageManager;
@@ -51,7 +52,6 @@ import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Vector;
 
 public class AnnotationLabels extends Panel
         implements ActionListener, MouseListener, MouseMotionListener
@@ -843,8 +843,8 @@ public class AnnotationLabels extends Panel
                     + "\t" + sq.getSequenceAsString() + "\n");
     if (av.hasHiddenColumns())
     {
-      jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector<>(
-              av.getAlignment().getHiddenColumns().getHiddenColumnsCopy());
+      jalview.appletgui.AlignFrame.copiedHiddenColumns = new HiddenColumns(
+              av.getAlignment().getHiddenColumns());
     }
   }
 
index 50a9e33..50bc184 100755 (executable)
@@ -480,7 +480,7 @@ public class AnnotationPanel extends Panel
     if (av.hasHiddenColumns())
     {
       column = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(column);
+              .visibleToAbsoluteColumn(column);
     }
 
     if (row > -1 && column < aa[row].annotations.length
index f5ea12e..296f898 100755 (executable)
@@ -286,7 +286,7 @@ public class IdCanvas extends Panel implements ViewportListenerI
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+              .absoluteToVisibleColumn(maxwidth) - 1;
     }
 
     int annotationHeight = 0;
index 04fb22b..c91449f 100755 (executable)
@@ -42,6 +42,7 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.beans.PropertyChangeEvent;
+import java.util.Iterator;
 import java.util.List;
 
 public class ScalePanel extends Panel
@@ -86,7 +87,7 @@ public class ScalePanel extends Panel
 
     if (av.hasHiddenColumns())
     {
-      res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(x);
+      res = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(x);
     }
     else
     {
@@ -173,7 +174,7 @@ public class ScalePanel extends Panel
       });
       pop.add(item);
 
-      if (av.getAlignment().getHiddenColumns().hasManyHiddenColumns())
+      if (av.getAlignment().getHiddenColumns().hasMultiHiddenColumnRegions())
       {
         item = new MenuItem(MessageManager.getString("action.reveal_all"));
         item.addActionListener(new ActionListener()
@@ -234,7 +235,7 @@ public class ScalePanel extends Panel
     if (av.hasHiddenColumns())
     {
       res = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(res);
+              .visibleToAbsoluteColumn(res);
     }
 
     if (!stretchingGroup)
@@ -275,7 +276,7 @@ public class ScalePanel extends Panel
     int res = (evt.getX() / av.getCharWidth())
             + av.getRanges().getStartRes();
     res = Math.max(0, res);
-    res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
+    res = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(res);
     res = Math.min(res, av.getAlignment().getWidth() - 1);
     min = Math.min(res, min);
     max = Math.max(res, max);
@@ -377,7 +378,7 @@ public class ScalePanel extends Panel
         {
           if (hidden.isVisible(sel))
           {
-            sel = hidden.findColumnPosition(sel);
+            sel = hidden.absoluteToVisibleColumn(sel);
           }
           else
           {
@@ -436,24 +437,17 @@ public class ScalePanel extends Panel
       if (av.getShowHiddenMarkers())
       {
         int widthx = 1 + endx - startx;
-        List<Integer> positions = hidden.findHiddenRegionPositions();
-        for (int pos : positions)
+        Iterator<Integer> it = hidden.getStartRegionIterator(startx,
+                startx + widthx + 1);
+        while (it.hasNext())
         {
-
-          res = pos - startx;
-
-          if (res < 0 || res > widthx)
-          {
-            continue;
-          }
+          res = it.next() - startx;
 
           gg.fillPolygon(
                   new int[]
-                  { -1 + res * avCharWidth - avcharHeight / 4,
-                      -1 + res * avCharWidth + avcharHeight / 4,
-                      -1 + res * avCharWidth },
-                  new int[]
-                  { y, y, y + 2 * yOf }, 3);
+                  { -1 + res * avCharWidth - avcharHeight / 4, -1 + res * avCharWidth + avcharHeight / 4,
+              -1 + res * avCharWidth }, new int[]
+          { y, y, y + 2 * yOf }, 3);
         }
       }
     }
index 2420cf7..35d73de 100755 (executable)
@@ -25,6 +25,7 @@ import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.VisibleContigsIterator;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
 import jalview.viewmodel.AlignmentViewport;
@@ -37,7 +38,7 @@ import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.Panel;
 import java.beans.PropertyChangeEvent;
-import java.util.List;
+import java.util.Iterator;
 
 public class SeqCanvas extends Panel implements ViewportListenerI
 {
@@ -130,16 +131,16 @@ public class SeqCanvas extends Panel implements ViewportListenerI
     if (av.hasHiddenColumns())
     {
       startx = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(startx);
+              .visibleToAbsoluteColumn(startx);
       endx = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(endx);
+              .visibleToAbsoluteColumn(endx);
     }
 
     int maxwidth = av.getAlignment().getWidth();
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+              .absoluteToVisibleColumn(maxwidth) - 1;
     }
 
     // WEST SCALE
@@ -180,7 +181,7 @@ public class SeqCanvas extends Panel implements ViewportListenerI
     if (av.hasHiddenColumns())
     {
       endx = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(endx);
+              .visibleToAbsoluteColumn(endx);
     }
 
     SequenceI seq;
@@ -417,71 +418,71 @@ public class SeqCanvas extends Panel implements ViewportListenerI
           int canvasHeight, int startRes)
   {
     AlignmentI al = av.getAlignment();
-
+  
     FontMetrics fm = getFontMetrics(av.getFont());
-
+  
     LABEL_EAST = 0;
     LABEL_WEST = 0;
-
+  
     if (av.getScaleRightWrapped())
     {
       LABEL_EAST = fm.stringWidth(getMask());
     }
-
+  
     if (av.getScaleLeftWrapped())
     {
       LABEL_WEST = fm.stringWidth(getMask());
     }
-
+  
     int hgap = avcharHeight;
     if (av.getScaleAboveWrapped())
     {
       hgap += avcharHeight;
     }
-
+  
     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
     int cHeight = av.getAlignment().getHeight() * avcharHeight;
-
+  
     av.setWrappedWidth(cWidth);
-
+  
     av.getRanges().setViewportStartAndWidth(startRes, cWidth);
-
+  
     int endx;
     int ypos = hgap;
-
+  
     int maxwidth = av.getAlignment().getWidth();
-
+  
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth);
+              .absoluteToVisibleColumn(maxwidth);
     }
-
+  
     while ((ypos <= canvasHeight) && (startRes < maxwidth))
     {
       endx = startRes + cWidth - 1;
-
+  
       if (endx > maxwidth)
       {
         endx = maxwidth;
       }
-
+  
       g.setColor(Color.black);
-
+  
       if (av.getScaleLeftWrapped())
       {
         drawWestScale(g, startRes, endx, ypos);
       }
-
+  
       if (av.getScaleRightWrapped())
       {
         g.translate(canvasWidth - LABEL_EAST, 0);
         drawEastScale(g, startRes, endx, ypos);
         g.translate(-(canvasWidth - LABEL_EAST), 0);
       }
-
+  
       g.translate(LABEL_WEST, 0);
-
+  
       if (av.getScaleAboveWrapped())
       {
         drawNorthScale(g, startRes, endx, ypos);
@@ -491,37 +492,27 @@ public class SeqCanvas extends Panel implements ViewportListenerI
         HiddenColumns hidden = av.getAlignment().getHiddenColumns();
         g.setColor(Color.blue);
         int res;
-        List<Integer> positions = hidden.findHiddenRegionPositions();
-        for (int pos : positions)
+        Iterator<Integer> it = hidden.getStartRegionIterator(startRes,
+                endx + 1);
+        while (it.hasNext())
         {
-          res = pos - startRes;
-
-          if (res < 0 || res > endx - startRes)
-          {
-            continue;
-          }
-
+          res = it.next() - startRes;
           gg.fillPolygon(
                   new int[]
-                  { res * avcharWidth - avcharHeight / 4,
-                      res * avcharWidth + avcharHeight / 4,
-                      res * avcharWidth },
+                  { res * avcharWidth - avcharHeight / 4, res * avcharWidth + avcharHeight / 4, res * avcharWidth },
                   new int[]
-                  { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2),
-                      ypos - (avcharHeight / 2) + 8 },
-                  3);
-
+                  { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2), ypos - (avcharHeight / 2) + 8 }, 3);
         }
       }
-
+  
       if (g.getClip() == null)
       {
         g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
       }
-
+  
       drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
       g.setClip(null);
-
+  
       if (av.isShowAnnotation())
       {
         g.translate(0, cHeight + ypos + 4);
@@ -529,17 +520,17 @@ public class SeqCanvas extends Panel implements ViewportListenerI
         {
           annotations = new AnnotationPanel(av);
         }
-
+  
         annotations.drawComponent(g, startRes, endx + 1);
         g.translate(0, -cHeight - ypos - 4);
       }
       g.translate(-LABEL_WEST, 0);
-
+  
       ypos += cHeight + getAnnotationHeight() + hgap;
-
+  
       startRes += cWidth;
     }
-
+  
   }
 
   AnnotationPanel annotations;
@@ -570,70 +561,44 @@ public class SeqCanvas extends Panel implements ViewportListenerI
     else
     {
       int screenY = 0;
-      final int screenYMax = endRes - startRes;
-      int blockStart = startRes;
-      int blockEnd = endRes;
-
-      if (av.hasHiddenColumns())
-      {
-        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-        for (int[] region : hidden.getHiddenColumnsCopy())
-        {
-          int hideStart = region[0];
-          int hideEnd = region[1];
-
-          if (hideStart <= blockStart)
-          {
-            blockStart += (hideEnd - hideStart) + 1;
-            continue;
-          }
-
-          /*
-           * draw up to just before the next hidden region, or the end of
-           * the visible region, whichever comes first
-           */
-          blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
-                  - screenY);
-
-          g1.translate(screenY * avcharWidth, 0);
+      int blockStart;
+      int blockEnd;
 
-          draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      VisibleContigsIterator regions = (VisibleContigsIterator) hidden
+              .getVisContigsIterator(startRes, endRes + 1, true);
 
-          /*
-           * draw the downline of the hidden column marker (ScalePanel draws the
-           * triangle on top) if we reached it
-           */
-          if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
-          {
-            g1.setColor(Color.blue);
-            g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
-                    0 + offset,
-                    (blockEnd - blockStart + 1) * avcharWidth - 1,
-                    (endSeq - startSeq + 1) * avcharHeight + offset);
-          }
-
-          g1.translate(-screenY * avcharWidth, 0);
-          screenY += blockEnd - blockStart + 1;
-          blockStart = hideEnd + 1;
-
-          if (screenY > screenYMax)
-          {
-            // already rendered last block
-            return;
-          }
-        }
-      }
-      if (screenY <= screenYMax)
+      while (regions.hasNext())
       {
-        // remaining visible region to render
-        blockEnd = blockStart + (endRes - startRes) - screenY;
+        int[] region = regions.next();
+        blockEnd = region[1];
+        blockStart = region[0];
+
+        /*
+         * draw up to just before the next hidden region, or the end of
+         * the visible region, whichever comes first
+         */
         g1.translate(screenY * avcharWidth, 0);
+
         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
 
+        /*
+         * draw the downline of the hidden column marker (ScalePanel draws the
+         * triangle on top) if we reached it
+         */
+        if (av.getShowHiddenMarkers()
+                && (regions.hasNext() || regions.endsAtHidden()))
+        {
+          g1.setColor(Color.blue);
+          g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
+                  0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1,
+                  (endSeq - startSeq + 1) * avcharHeight + offset);
+        }
+
         g1.translate(-screenY * avcharWidth, 0);
+        screenY += blockEnd - blockStart + 1;
       }
     }
-
   }
 
   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
index d74bbb7..e07dae6 100644 (file)
@@ -647,7 +647,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     if (av.hasHiddenColumns())
     {
       res = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(res);
+              .visibleToAbsoluteColumn(res);
     }
 
     return res;
@@ -1123,9 +1123,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     {
       fixedColumns = true;
       int y1 = av.getAlignment().getHiddenColumns()
-              .getHiddenBoundaryLeft(startres);
+              .getNextHiddenBoundary(true, startres);
       int y2 = av.getAlignment().getHiddenColumns()
-              .getHiddenBoundaryRight(startres);
+              .getNextHiddenBoundary(false, startres);
 
       if ((insertGap && startres > y1 && lastres < y1)
               || (!insertGap && startres < y2 && lastres > y2))
@@ -1197,7 +1197,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
           if (sg.getSize() == av.getAlignment().getHeight())
           {
             if ((av.hasHiddenColumns() && startres < av.getAlignment()
-                    .getHiddenColumns().getHiddenBoundaryRight(startres)))
+                    .getHiddenColumns()
+                    .getNextHiddenBoundary(false, startres)))
             {
               endEditing();
               return;
index 6504290..a60496c 100644 (file)
@@ -31,7 +31,6 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
 import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
@@ -471,7 +470,7 @@ public class JalviewLite extends Applet
         SequenceI rs = sel.getSequenceAt(0);
         start = rs.findIndex(start);
         end = rs.findIndex(end);
-        List<Integer> cs = new ArrayList<Integer>(csel.getSelected());
+        List<Integer> cs = new ArrayList<>(csel.getSelected());
         csel.clear();
         for (Integer selectedCol : cs)
         {
@@ -921,7 +920,7 @@ public class JalviewLite extends Applet
     setMouseoverListener(currentAlignFrame, listener);
   }
 
-  private Vector<jalview.javascript.JSFunctionExec> javascriptListeners = new Vector<jalview.javascript.JSFunctionExec>();
+  private Vector<jalview.javascript.JSFunctionExec> javascriptListeners = new Vector<>();
 
   /*
    * (non-Javadoc)
@@ -2165,8 +2164,8 @@ public class JalviewLite extends Applet
           else
           {
             param = st.nextToken();
-            List<SequenceI> tmp = new ArrayList<SequenceI>();
-            List<String> tmp2 = new ArrayList<String>();
+            List<SequenceI> tmp = new ArrayList<>();
+            List<String> tmp2 = new ArrayList<>();
 
             while (st.hasMoreTokens())
             {
@@ -2279,12 +2278,9 @@ public class JalviewLite extends Applet
           JnetAnnotationMaker.add_annotation(predictions,
                   alignFrame.viewport.getAlignment(), 0, false);
           // false == do not add sequence profile from concise output
-          SequenceI repseq = alignFrame.viewport.getAlignment()
-                  .getSequenceAt(0);
-          alignFrame.viewport.getAlignment().setSeqrep(repseq);
-          HiddenColumns cs = new HiddenColumns();
-          cs.hideInsertionsFor(repseq);
-          alignFrame.viewport.getAlignment().setHiddenColumns(cs);
+
+          alignFrame.viewport.getAlignment().setupJPredAlignment();
+
           alignFrame.alignPanel.fontChanged();
           alignFrame.alignPanel.setScrollValues(0, 0);
           result = true;
@@ -2802,9 +2798,9 @@ public class JalviewLite extends Applet
     // callInitCallback();
   }
 
-  private Hashtable<String, long[]> jshashes = new Hashtable<String, long[]>();
+  private Hashtable<String, long[]> jshashes = new Hashtable<>();
 
-  private Hashtable<String, Hashtable<String, String[]>> jsmessages = new Hashtable<String, Hashtable<String, String[]>>();
+  private Hashtable<String, Hashtable<String, String[]>> jsmessages = new Hashtable<>();
 
   public void setJsMessageSet(String messageclass, String viewId,
           String[] colcommands)
@@ -2812,7 +2808,7 @@ public class JalviewLite extends Applet
     Hashtable<String, String[]> msgset = jsmessages.get(messageclass);
     if (msgset == null)
     {
-      msgset = new Hashtable<String, String[]>();
+      msgset = new Hashtable<>();
       jsmessages.put(messageclass, msgset);
     }
     msgset.put(viewId, colcommands);
index a3b3ff6..3ba35b6 100755 (executable)
@@ -29,10 +29,12 @@ import jalview.util.MessageManager;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -1924,4 +1926,117 @@ public class Alignment implements AlignmentI
   {
     hiddenCols = cols;
   }
+
+  @Override
+  public void setupJPredAlignment()
+  {
+    SequenceI repseq = getSequenceAt(0);
+    setSeqrep(repseq);
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideList(repseq.getInsertions());
+    setHiddenColumns(cs);
+  }
+
+  @Override
+  public HiddenColumns propagateInsertions(SequenceI profileseq,
+          AlignmentView input)
+  {
+    int profsqpos = 0;
+
+    char gc = getGapCharacter();
+    Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
+    HiddenColumns nview = (HiddenColumns) alandhidden[1];
+    SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
+    return propagateInsertions(profileseq, origseq, nview);
+  }
+
+  /**
+   * 
+   * @param profileseq
+   *          sequence in al which corresponds to origseq
+   * @param al
+   *          alignment which is to have gaps inserted into it
+   * @param origseq
+   *          sequence corresponding to profileseq which defines gap map for
+   *          modifying al
+   */
+  private HiddenColumns propagateInsertions(SequenceI profileseq,
+          SequenceI origseq, HiddenColumns hc)
+  {
+    // take the set of hidden columns, and the set of gaps in origseq,
+    // and remove all the hidden gaps from hiddenColumns
+
+    // first get the gaps as a Bitset
+    // then calculate hidden ^ not(gap)
+    BitSet gaps = origseq.gapBitset();
+    hc.andNot(gaps);
+
+    // for each sequence in the alignment, except the profile sequence,
+    // insert gaps corresponding to each hidden region but where each hidden
+    // column region is shifted backwards by the number of preceding visible
+    // gaps update hidden columns at the same time
+    HiddenColumns newhidden = new HiddenColumns();
+
+    int numGapsBefore = 0;
+    int gapPosition = 0;
+    Iterator<int[]> it = hc.iterator();
+    while (it.hasNext())
+    {
+      int[] region = it.next();
+
+      // get region coordinates accounting for gaps
+      // we can rely on gaps not being *in* hidden regions because we already
+      // removed those
+      while (gapPosition < region[0])
+      {
+        gapPosition++;
+        if (gaps.get(gapPosition))
+        {
+          numGapsBefore++;
+        }
+      }
+
+      int left = region[0] - numGapsBefore;
+      int right = region[1] - numGapsBefore;
+
+      newhidden.hideColumns(left, right);
+      padGaps(left, right, profileseq);
+    }
+    return newhidden;
+  }
+
+  /**
+   * Pad gaps in all sequences in alignment except profileseq
+   * 
+   * @param left
+   *          position of first gap to insert
+   * @param right
+   *          position of last gap to insert
+   * @param profileseq
+   *          sequence not to pad
+   */
+  private void padGaps(int left, int right, SequenceI profileseq)
+  {
+    char gc = getGapCharacter();
+
+    // make a string with number of gaps = length of hidden region
+    StringBuilder sb = new StringBuilder();
+    for (int g = 0; g < right - left + 1; g++)
+    {
+      sb.append(gc);
+    }
+
+    // loop over the sequences and pad with gaps where required
+    for (int s = 0, ns = getHeight(); s < ns; s++)
+    {
+      SequenceI sqobj = getSequenceAt(s);
+      if ((sqobj != profileseq) && (sqobj.getLength() >= left))
+      {
+        String sq = sqobj.getSequenceAsString();
+        sqobj.setSequence(
+                sq.substring(0, left) + sb.toString() + sq.substring(left));
+      }
+    }
+  }
+
 }
index f7bf4d8..0098d76 100755 (executable)
@@ -666,7 +666,7 @@ public class AlignmentAnnotation
     this.calcId = annotation.calcId;
     if (annotation.properties != null)
     {
-      properties = new HashMap<String, String>();
+      properties = new HashMap<>();
       for (Map.Entry<String, String> val : annotation.properties.entrySet())
       {
         properties.put(val.getKey(), val.getValue());
@@ -702,7 +702,7 @@ public class AlignmentAnnotation
       if (annotation.sequenceMapping != null)
       {
         Integer p = null;
-        sequenceMapping = new HashMap<Integer, Annotation>();
+        sequenceMapping = new HashMap<>();
         Iterator<Integer> pos = annotation.sequenceMapping.keySet()
                 .iterator();
         while (pos.hasNext())
@@ -782,7 +782,7 @@ public class AlignmentAnnotation
       int epos = sequenceRef.findPosition(endRes);
       if (sequenceMapping != null)
       {
-        Map<Integer, Annotation> newmapping = new HashMap<Integer, Annotation>();
+        Map<Integer, Annotation> newmapping = new HashMap<>();
         Iterator<Integer> e = sequenceMapping.keySet().iterator();
         while (e.hasNext())
         {
@@ -909,7 +909,7 @@ public class AlignmentAnnotation
     {
       return;
     }
-    sequenceMapping = new HashMap<Integer, Annotation>();
+    sequenceMapping = new HashMap<>();
 
     int seqPos;
 
@@ -1124,7 +1124,7 @@ public class AlignmentAnnotation
     {
       return;
     }
-    hidden.makeVisibleAnnotation(this);
+    makeVisibleAnnotation(hidden);
   }
 
   public void setPadGaps(boolean padgaps, char gapchar)
@@ -1190,7 +1190,7 @@ public class AlignmentAnnotation
   /**
    * properties associated with the calcId
    */
-  protected Map<String, String> properties = new HashMap<String, String>();
+  protected Map<String, String> properties = new HashMap<>();
 
   /**
    * base colour for line graphs. If null, will be set automatically by
@@ -1236,7 +1236,7 @@ public class AlignmentAnnotation
             : false;
 
     // TODO build a better annotation element map and get rid of annotations[]
-    Map<Integer, Annotation> mapForsq = new HashMap<Integer, Annotation>();
+    Map<Integer, Annotation> mapForsq = new HashMap<>();
     if (sequenceMapping != null)
     {
       if (sp2sq != null)
@@ -1289,7 +1289,7 @@ public class AlignmentAnnotation
     if (mapping != null)
     {
       Map<Integer, Annotation> old = sequenceMapping;
-      Map<Integer, Annotation> remap = new HashMap<Integer, Annotation>();
+      Map<Integer, Annotation> remap = new HashMap<>();
       int index = -1;
       for (int mp[] : mapping.values())
       {
@@ -1347,7 +1347,7 @@ public class AlignmentAnnotation
   {
     if (properties == null)
     {
-      properties = new HashMap<String, String>();
+      properties = new HashMap<>();
     }
     properties.put(property, value);
   }
@@ -1473,6 +1473,115 @@ public class AlignmentAnnotation
     return graphMin < graphMax;
   }
 
+  /**
+   * delete any columns in alignmentAnnotation that are hidden (including
+   * sequence associated annotation).
+   * 
+   * @param hiddenColumns
+   *          the set of hidden columns
+   */
+  public void makeVisibleAnnotation(HiddenColumns hiddenColumns)
+  {
+    if (annotations != null)
+    {
+      makeVisibleAnnotation(0, annotations.length, hiddenColumns);
+    }
+  }
+
+  /**
+   * delete any columns in alignmentAnnotation that are hidden (including
+   * sequence associated annotation).
+   * 
+   * @param start
+   *          remove any annotation to the right of this column
+   * @param end
+   *          remove any annotation to the left of this column
+   * @param hiddenColumns
+   *          the set of hidden columns
+   */
+  public void makeVisibleAnnotation(int start, int end,
+          HiddenColumns hiddenColumns)
+  {
+    if (annotations != null)
+    {
+      if (hiddenColumns.hasHiddenColumns())
+      {
+        removeHiddenAnnotation(start, end, hiddenColumns);
+      }
+      else
+      {
+        restrict(start, end);
+      }
+    }
+  }
+
+  /**
+   * The actual implementation of deleting hidden annotation columns
+   * 
+   * @param start
+   *          remove any annotation to the right of this column
+   * @param end
+   *          remove any annotation to the left of this column
+   * @param hiddenColumns
+   *          the set of hidden columns
+   */
+  private void removeHiddenAnnotation(int start, int end,
+          HiddenColumns hiddenColumns)
+  {
+    // mangle the alignmentAnnotation annotation array
+    ArrayList<Annotation[]> annels = new ArrayList<>();
+    Annotation[] els = null;
+
+    int w = 0;
+
+    Iterator<int[]> blocks = hiddenColumns.getVisContigsIterator(start,
+            end + 1, false);
+
+    int copylength;
+    int annotationLength;
+    while (blocks.hasNext())
+    {
+      int[] block = blocks.next();
+      annotationLength = block[1] - block[0] + 1;
+
+      if (blocks.hasNext())
+      {
+        // copy just the visible segment of the annotation row
+        copylength = annotationLength;
+      }
+      else
+      {
+        if (annotationLength + block[0] <= annotations.length)
+        {
+          // copy just the visible segment of the annotation row
+          copylength = annotationLength;
+        }
+        else
+        {
+          // copy to the end of the annotation row
+          copylength = annotations.length - block[0];
+        }
+      }
+
+      els = new Annotation[annotationLength];
+      annels.add(els);
+      System.arraycopy(annotations, block[0], els, 0, copylength);
+      w += annotationLength;
+    }
+
+    if (w != 0)
+    {
+      annotations = new Annotation[w];
+
+      w = 0;
+      for (Annotation[] chnk : annels)
+      {
+        System.arraycopy(chnk, 0, annotations, w, chnk.length);
+        w += chnk.length;
+      }
+    }
+  }
+
   public static Iterable<AlignmentAnnotation> findAnnotations(
           Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId,
           String label)
index 084b80e..5fb16d6 100755 (executable)
@@ -580,6 +580,32 @@ public interface AlignmentI extends AnnotatedCollectionI
    */
   AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo);
 
+  /**
+   * Set the hidden columns collection on the alignment
+   * 
+   * @param cols
+   */
   public void setHiddenColumns(HiddenColumns cols);
 
+  /**
+   * Set the first sequence as representative and hide its insertions. Typically
+   * used when loading JPred files.
+   */
+  public void setupJPredAlignment();
+
+  /**
+   * Add gaps into the sequences aligned to profileseq under the given
+   * AlignmentView
+   * 
+   * @param profileseq
+   *          sequence in al which sequences are aligned to
+   * @param input
+   *          alignment view where sequence corresponding to profileseq is first
+   *          entry
+   * @return new HiddenColumns for new alignment view, with insertions into
+   *         profileseq marked as hidden.
+   */
+  public HiddenColumns propagateInsertions(SequenceI profileseq,
+          AlignmentView input);
+
 }
index 1723f1d..17e9ea6 100644 (file)
@@ -20,7 +20,7 @@
  */
 package jalview.datamodel;
 
-import java.util.List;
+import java.util.Iterator;
 
 public class CigarArray extends CigarBase
 {
@@ -90,9 +90,7 @@ public class CigarArray extends CigarBase
           SequenceGroup selectionGroup)
   {
     this(constructSeqCigarArray(alignment, selectionGroup));
-    constructFromAlignment(alignment,
-            hidden != null ? hidden.getHiddenColumnsCopy() : null,
-            selectionGroup);
+    constructFromAlignment(alignment, hidden, selectionGroup);
   }
 
   private static int[] _calcStartEndBounds(AlignmentI alignment,
@@ -154,33 +152,25 @@ public class CigarArray extends CigarBase
    * @param selectionGroup
    */
   private void constructFromAlignment(AlignmentI alignment,
-          List<int[]> list, SequenceGroup selectionGroup)
+          HiddenColumns hidden, SequenceGroup selectionGroup)
   {
     int[] _startend = _calcStartEndBounds(alignment, selectionGroup);
-    int start = _startend[1], end = _startend[2];
+    int start = _startend[1];
+    int end = _startend[2];
     // now construct the CigarArray operations
-    if (list != null)
+    if (hidden != null)
     {
       int[] region;
-      int hideStart, hideEnd;
+      int hideStart;
+      int hideEnd;
       int last = start;
-      for (int j = 0; last < end & j < list.size(); j++)
+
+      Iterator<int[]> regions = hidden.getBoundedIterator(start, end);
+      while (regions.hasNext())
       {
-        region = list.get(j);
+        region = regions.next();
         hideStart = region[0];
         hideEnd = region[1];
-        // edit hidden regions to selection range
-
-        // just move on if hideEnd is before last
-        if (hideEnd < last)
-        {
-          continue;
-        }
-        // exit if next region is after end
-        if (hideStart > end)
-        {
-          break;
-        }
 
         // truncate region at start if last falls in region
         if ((hideStart < last) && (hideEnd >= last))
@@ -204,6 +194,7 @@ public class CigarArray extends CigarBase
         addOperation(CigarArray.D, 1 + hideEnd - hideStart);
         last = hideEnd + 1;
       }
+
       // Final match if necessary.
       if (last <= end)
       {
index c0a43ee..a7e93da 100644 (file)
  */
 package jalview.datamodel;
 
-import jalview.util.Comparison;
-import jalview.util.ShiftList;
-
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
-import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Vector;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+/**
+ * This class manages the collection of hidden columns associated with an
+ * alignment. To iterate over the collection, or over visible columns/regions,
+ * use an iterator obtained from one of:
+ * 
+ * - getBoundedIterator: iterates over the hidden regions, within some bounds,
+ * returning *absolute* positions
+ * 
+ * - getBoundedStartIterator: iterates over the start positions of hidden
+ * regions, within some bounds, returning *visible* positions
+ * 
+ * - getVisContigsIterator: iterates over visible regions in a range, returning
+ * *absolute* positions
+ * 
+ * - getVisibleColsIterator: iterates over the visible *columns*
+ * 
+ * For performance reasons, provide bounds where possible. Note that column
+ * numbering begins at 0 throughout this class.
+ * 
+ * @author kmourao
+ */
+
+/* Implementation notes:
+ * 
+ * Methods which change the hiddenColumns collection should use a writeLock to
+ * prevent other threads accessing the hiddenColumns collection while changes
+ * are being made. They should also reset the hidden columns cursor, and either
+ * update the hidden columns count, or set it to 0 (so that it will later be
+ * updated when needed).
+ * 
+ * 
+ * Methods which only need read access to the hidden columns collection should
+ * use a readLock to prevent other threads changing the hidden columns
+ * collection while it is in use.
+ */
 public class HiddenColumns
 {
+  private static final int HASH_MULTIPLIER = 31;
+
   private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
 
   /*
+   * Cursor which tracks the last used hidden columns region, and the number 
+   * of hidden columns up to (but not including) that region.
+   */
+  private HiddenColumnsCursor cursor = new HiddenColumnsCursor();
+
+  /*
+   * cache of the number of hidden columns: must be kept up to date by methods 
+   * which add or remove hidden columns
+   */
+  private int numColumns = 0;
+
+  /*
    * list of hidden column [start, end] ranges; the list is maintained in
    * ascending start column order
    */
-  private ArrayList<int[]> hiddenColumns;
+  private List<int[]> hiddenColumns = new ArrayList<>();
 
   /**
    * Constructor
@@ -51,19 +97,263 @@ public class HiddenColumns
    * Copy constructor
    * 
    * @param copy
+   *          the HiddenColumns object to copy from
    */
   public HiddenColumns(HiddenColumns copy)
   {
+    this(copy, Integer.MIN_VALUE, Integer.MAX_VALUE, 0);
+  }
+
+  /**
+   * Copy constructor within bounds and with offset. Copies hidden column
+   * regions fully contained between start and end, and offsets positions by
+   * subtracting offset.
+   * 
+   * @param copy
+   *          HiddenColumns instance to copy from
+   * @param start
+   *          lower bound to copy from
+   * @param end
+   *          upper bound to copy to
+   * @param offset
+   *          offset to subtract from each region boundary position
+   * 
+   */
+  public HiddenColumns(HiddenColumns copy, int start, int end, int offset)
+  {
     try
     {
       LOCK.writeLock().lock();
       if (copy != null)
       {
-        if (copy.hiddenColumns != null)
+        numColumns = 0;
+        Iterator<int[]> it = copy.getBoundedIterator(start, end);
+        while (it.hasNext())
+        {
+          int[] region = it.next();
+          // still need to check boundaries because iterator returns
+          // all overlapping regions and we need contained regions
+          if (region[0] >= start && region[1] <= end)
+          {
+            hiddenColumns.add(
+                    new int[]
+            { region[0] - offset, region[1] - offset });
+            numColumns += region[1] - region[0] + 1;
+          }
+        }
+        cursor = new HiddenColumnsCursor(hiddenColumns);
+      }
+    } finally
+    {
+      LOCK.writeLock().unlock();
+    }
+  }
+
+  /**
+   * Adds the specified column range to the hidden columns collection
+   * 
+   * @param start
+   *          start of range to add (absolute position in alignment)
+   * @param end
+   *          end of range to add (absolute position in alignment)
+   */
+  public void hideColumns(int start, int end)
+  {
+    try
+    {
+      LOCK.writeLock().lock();
+
+      int previndex = 0;
+      int prevHiddenCount = 0;
+      int regionindex = 0;
+      if (!hiddenColumns.isEmpty())
+      {
+        // set up cursor reset values
+        HiddenCursorPosition cursorPos = cursor.findRegionForColumn(start, false);
+        regionindex = cursorPos.getRegionIndex();
+
+        if (regionindex > 0)
+        {
+          // get previous index and hidden count for updating the cursor later
+          previndex = regionindex - 1;
+          int[] prevRegion = hiddenColumns.get(previndex);
+          prevHiddenCount = cursorPos.getHiddenSoFar()
+                  - (prevRegion[1] - prevRegion[0] + 1);
+        }
+      }
+
+      // new range follows everything else; check first to avoid looping over
+      // whole hiddenColumns collection
+      if (hiddenColumns.isEmpty()
+              || start > hiddenColumns.get(hiddenColumns.size() - 1)[1])
+      {
+        hiddenColumns.add(new int[] { start, end });
+        numColumns += end - start + 1;
+      }
+      else
+      {
+        /*
+         * traverse existing hidden ranges and insert / amend / append as
+         * appropriate
+         */
+        boolean added = false;
+        if (regionindex > 0)
+        {
+          added = insertRangeAtRegion(regionindex - 1, start, end);
+        }
+        if (!added && regionindex < hiddenColumns.size())
+        {
+          insertRangeAtRegion(regionindex, start, end);
+        }
+      }
+
+      // reset the cursor to just before our insertion point: this saves
+      // a lot of reprocessing in large alignments
+      cursor = new HiddenColumnsCursor(hiddenColumns, previndex,
+              prevHiddenCount);
+    } finally
+    {
+      LOCK.writeLock().unlock();
+    }
+  }
+
+  /**
+   * Insert [start, range] at the region at index i in hiddenColumns, if
+   * feasible
+   * 
+   * @param i
+   *          index to insert at
+   * @param start
+   *          start of range to insert
+   * @param end
+   *          end of range to insert
+   * @return true if range was successfully inserted
+   */
+  private boolean insertRangeAtRegion(int i, int start, int end)
+  {
+    boolean added = false;
+
+    int[] region = hiddenColumns.get(i);
+    if (end < region[0] - 1)
+    {
+      /*
+       * insert discontiguous preceding range
+       */
+      hiddenColumns.add(i, new int[] { start, end });
+      numColumns += end - start + 1;
+      added = true;
+    }
+    else if (end <= region[1])
+    {
+      /*
+       * new range overlaps existing, or is contiguous preceding it - adjust
+       * start column
+       */
+      int oldstart = region[0];
+      region[0] = Math.min(region[0], start);
+      numColumns += oldstart - region[0]; // new columns are between old and
+                                              // adjusted starts
+      added = true;
+    }
+    else if (start <= region[1] + 1)
+    {
+      /*
+       * new range overlaps existing, or is contiguous following it - adjust
+       * start and end columns
+       */
+      insertRangeAtOverlap(i, start, end, region);
+      added = true;
+    }
+    return added;
+  }
+
+  /**
+   * Insert a range whose start position overlaps an existing region and/or is
+   * contiguous to the right of the region
+   * 
+   * @param i
+   *          index to insert at
+   * @param start
+   *          start of range to insert
+   * @param end
+   *          end of range to insert
+   * @param region
+   *          the overlapped/continued region
+   */
+  private void insertRangeAtOverlap(int i, int start, int end, int[] region)
+  {
+    int oldstart = region[0];
+    int oldend = region[1];
+    region[0] = Math.min(region[0], start);
+    region[1] = Math.max(region[1], end);
+
+    numColumns += oldstart - region[0];
+
+    /*
+     * also update or remove any subsequent ranges 
+     * that are overlapped
+     */
+    int endi = i;
+    while (endi < hiddenColumns.size() - 1)
+    {
+      int[] nextRegion = hiddenColumns.get(endi + 1);
+      if (nextRegion[0] > end + 1)
+      {
+        /*
+         * gap to next hidden range - no more to update
+         */
+        break;
+      }
+      numColumns -= nextRegion[1] - nextRegion[0] + 1;
+      region[1] = Math.max(nextRegion[1], end);
+      endi++;
+    }
+    numColumns += region[1] - oldend;
+    hiddenColumns.subList(i + 1, endi + 1).clear();
+  }
+
+  /**
+   * hide a list of ranges
+   * 
+   * @param ranges
+   */
+  public void hideList(List<int[]> ranges)
+  {
+    try
+    {
+      LOCK.writeLock().lock();
+      for (int[] r : ranges)
+      {
+        hideColumns(r[0], r[1]);
+      }
+      cursor = new HiddenColumnsCursor(hiddenColumns);
+
+    } finally
+    {
+      LOCK.writeLock().unlock();
+    }
+  }
+
+  /**
+   * Unhides, and adds to the selection list, all hidden columns
+   */
+  public void revealAllHiddenColumns(ColumnSelection sel)
+  {
+    try
+    {
+      LOCK.writeLock().lock();
+
+      for (int[] region : hiddenColumns)
+      {
+        for (int j = region[0]; j < region[1] + 1; j++)
         {
-          hiddenColumns = copy.copyHiddenRegionsToArrayList();
+          sel.addElement(j);
         }
       }
+      hiddenColumns.clear();
+      cursor = new HiddenColumnsCursor(hiddenColumns);
+      numColumns = 0;
+
     } finally
     {
       LOCK.writeLock().unlock();
@@ -71,16 +361,46 @@ public class HiddenColumns
   }
 
   /**
-   * This method is used to return all the HiddenColumn regions and is intended
-   * to remain private. External callers which need a copy of the regions can
-   * call getHiddenColumnsCopyAsList.
+   * Reveals, and marks as selected, the hidden column range with the given
+   * start column
    * 
-   * @return empty list or List of hidden column intervals
+   * @param start
+   *          the start column to look for
+   * @param sel
+   *          the column selection to add the hidden column range to
    */
-  private List<int[]> getHiddenRegions()
+  public void revealHiddenColumns(int start, ColumnSelection sel)
   {
-    return hiddenColumns == null ? Collections.<int[]> emptyList()
-            : hiddenColumns;
+    try
+    {
+      LOCK.writeLock().lock();
+
+      if (!hiddenColumns.isEmpty())
+      {
+        int regionIndex = cursor.findRegionForColumn(start, false)
+                .getRegionIndex();
+
+        if (regionIndex != -1 && regionIndex != hiddenColumns.size())
+        {
+          // regionIndex is the region which either contains start
+          // or lies to the right of start
+          int[] region = hiddenColumns.get(regionIndex);
+          if (start == region[0])
+          {
+            for (int j = region[0]; j < region[1] + 1; j++)
+            {
+              sel.addElement(j);
+            }
+            int colsToRemove = region[1] - region[0] + 1;
+            hiddenColumns.remove(regionIndex);
+            numColumns -= colsToRemove;
+          }
+        }
+      }
+    } finally
+    {
+      LOCK.writeLock().unlock();
+    }
   }
 
   /**
@@ -99,16 +419,22 @@ public class HiddenColumns
     {
       LOCK.readLock().lock();
       StringBuilder regionBuilder = new StringBuilder();
-      if (hiddenColumns != null)
+
+      boolean first = true;
+      for (int[] range : hiddenColumns)
       {
-        for (int[] range : hiddenColumns)
+        if (!first)
+        {
+          regionBuilder.append(delimiter);
+        }
+        else
         {
-          regionBuilder.append(delimiter).append(range[0]).append(between)
-                  .append(range[1]);
+          first = false;
         }
+        regionBuilder.append(range[0]).append(between).append(range[1]);
 
-        regionBuilder.deleteCharAt(0);
       }
+
       return regionBuilder.toString();
     } finally
     {
@@ -123,18 +449,20 @@ public class HiddenColumns
    */
   public int getSize()
   {
+    return numColumns;
+  }
+
+  /**
+   * Get the number of distinct hidden regions
+   * 
+   * @return number of regions
+   */
+  public int getNumberOfRegions()
+  {
     try
     {
       LOCK.readLock().lock();
-      int size = 0;
-      if (hasHiddenColumns())
-      {
-        for (int[] range : hiddenColumns)
-        {
-          size += range[1] - range[0] + 1;
-        }
-      }
-      return size;
+      return hiddenColumns.size();
     } finally
     {
       LOCK.readLock().unlock();
@@ -157,25 +485,23 @@ public class HiddenColumns
       /*
        * check hidden columns are either both null, or match
        */
-      if (this.hiddenColumns == null)
-      {
-        return (that.hiddenColumns == null);
-      }
-      if (that.hiddenColumns == null
-              || that.hiddenColumns.size() != this.hiddenColumns.size())
+
+      if (that.hiddenColumns.size() != this.hiddenColumns.size())
       {
         return false;
       }
-      int i = 0;
-      for (int[] thisRange : hiddenColumns)
+
+      Iterator<int[]> it = this.iterator();
+      Iterator<int[]> thatit = that.iterator();
+      while (it.hasNext())
       {
-        int[] thatRange = that.hiddenColumns.get(i++);
-        if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
+        if (!(Arrays.equals(it.next(), thatit.next())))
         {
           return false;
         }
       }
       return true;
+
     } finally
     {
       LOCK.readLock().unlock();
@@ -189,23 +515,19 @@ public class HiddenColumns
    *          int column index in alignment view (count from zero)
    * @return alignment column index for column
    */
-  public int adjustForHiddenColumns(int column)
+  public int visibleToAbsoluteColumn(int column)
   {
     try
     {
       LOCK.readLock().lock();
       int result = column;
-      if (hiddenColumns != null)
+
+      if (!hiddenColumns.isEmpty())
       {
-        for (int i = 0; i < hiddenColumns.size(); i++)
-        {
-          int[] region = hiddenColumns.get(i);
-          if (result >= region[0])
-          {
-            result += region[1] - region[0] + 1;
-          }
-        }
+        result += cursor.findRegionForColumn(column, true)
+                .getHiddenSoFar();
       }
+
       return result;
     } finally
     {
@@ -216,54 +538,52 @@ public class HiddenColumns
   /**
    * Use this method to find out where a column will appear in the visible
    * alignment when hidden columns exist. If the column is not visible, then the
-   * left-most visible column will always be returned.
+   * index of the next visible column on the left will be returned (or 0 if
+   * there is no visible column on the left)
    * 
    * @param hiddenColumn
    *          the column index in the full alignment including hidden columns
    * @return the position of the column in the visible alignment
    */
-  public int findColumnPosition(int hiddenColumn)
+  public int absoluteToVisibleColumn(int hiddenColumn)
   {
     try
     {
       LOCK.readLock().lock();
       int result = hiddenColumn;
-      if (hiddenColumns != null)
-      {
-        int index = 0;
-        int[] region;
-        do
-        {
-          region = hiddenColumns.get(index++);
-          if (hiddenColumn > region[1])
-          {
-            result -= region[1] + 1 - region[0];
-          }
-        } while ((hiddenColumn > region[1])
-                && (index < hiddenColumns.size()));
 
-        if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
+      if (!hiddenColumns.isEmpty())
+      {
+        HiddenCursorPosition cursorPos = cursor
+                .findRegionForColumn(hiddenColumn, false);
+        int index = cursorPos.getRegionIndex();
+        int hiddenBeforeCol = cursorPos.getHiddenSoFar();
+    
+        // just subtract hidden cols count - this works fine if column is
+        // visible
+        result = hiddenColumn - hiddenBeforeCol;
+    
+        // now check in case column is hidden - it will be in the returned
+        // hidden region
+        if (index < hiddenColumns.size())
         {
-          // Here the hidden column is within a region, so
-          // we want to return the position of region[0]-1, adjusted for any
-          // earlier hidden columns.
-          // Calculate the difference between the actual hidden col position
-          // and region[0]-1, and then subtract from result to convert result
-          // from
-          // the adjusted hiddenColumn value to the adjusted region[0]-1 value
-
-          // However, if the region begins at 0 we cannot return region[0]-1
-          // just return 0
-          if (region[0] == 0)
-          {
-            return 0;
-          }
-          else
+          int[] region = hiddenColumns.get(index);
+          if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
           {
-            return result - (hiddenColumn - region[0] + 1);
+            // actually col is hidden, return region[0]-1
+            // unless region[0]==0 in which case return 0
+            if (region[0] == 0)
+            {
+              result = 0;
+            }
+            else
+            {
+              result = region[0] - 1 - hiddenBeforeCol;
+            }
           }
         }
       }
+
       return result; // return the shifted position after removing hidden
                      // columns.
     } finally
@@ -274,113 +594,75 @@ public class HiddenColumns
 
   /**
    * Find the visible column which is a given visible number of columns to the
-   * left of another visible column. i.e. for a startColumn x, the column which
-   * is distance 1 away will be column x-1.
+   * left (negative visibleDistance) or right (positive visibleDistance) of
+   * startColumn. If startColumn is not visible, we use the visible column at
+   * the left boundary of the hidden region containing startColumn.
    * 
    * @param visibleDistance
-   *          the number of visible columns to offset by
+   *          the number of visible columns to offset by (left offset = negative
+   *          value; right offset = positive value)
    * @param startColumn
-   *          the column to start from
-   * @return the position of the column in the visible alignment
+   *          the position of the column to start from (absolute position)
+   * @return the position of the column which is <visibleDistance> away
+   *         (absolute position)
    */
-  public int subtractVisibleColumns(int visibleDistance, int startColumn)
+  public int offsetByVisibleColumns(int visibleDistance, int startColumn)
   {
     try
     {
-
       LOCK.readLock().lock();
-      int distance = visibleDistance;
-
-      // in case startColumn is in a hidden region, move it to the left
-      int start = adjustForHiddenColumns(findColumnPosition(startColumn));
-
-      // get index of hidden region to left of start
-      int index = getHiddenIndexLeft(start);
-      if (index == -1)
-      {
-        // no hidden regions to left of startColumn
-        return start - distance;
-      }
-
-      // walk backwards through the alignment subtracting the counts of visible
-      // columns from distance
-      int[] region;
-      int gap = 0;
-      int nextstart = start;
-
-      while ((index > -1) && (distance - gap > 0))
-      {
-        // subtract the gap to right of region from distance
-        distance -= gap;
-        start = nextstart;
-
-        // calculate the next gap
-        region = hiddenColumns.get(index);
-        gap = start - region[1];
-
-        // set start to just to left of current region
-        nextstart = region[0] - 1;
-        index--;
-      }
+      int start = absoluteToVisibleColumn(startColumn);
+      return visibleToAbsoluteColumn(start + visibleDistance);
 
-      if (distance - gap > 0)
-      {
-        // fell out of loop because there are no more hidden regions
-        distance -= gap;
-        return nextstart - distance;
-      }
-      return start - distance;
     } finally
     {
       LOCK.readLock().unlock();
     }
-
   }
 
   /**
-   * Use this method to determine the set of hiddenRegion start positions
+   * This method returns the rightmost limit of a region of an alignment with
+   * hidden columns. In otherwords, the next hidden column.
    * 
-   * @return list of column number in visible view where hidden regions start
+   * @param alPos
+   *          the absolute (visible) alignmentPosition to find the next hidden
+   *          column for
+   * @return the index of the next hidden column, or alPos if there is no next
+   *         hidden column
    */
-  public List<Integer> findHiddenRegionPositions()
+  public int getNextHiddenBoundary(boolean left, int alPos)
   {
     try
     {
       LOCK.readLock().lock();
-      List<Integer> positions = null;
-
-      if (hiddenColumns != null)
+      if (!hiddenColumns.isEmpty())
       {
-        positions = new ArrayList<>(hiddenColumns.size());
+        int index = cursor.findRegionForColumn(alPos, false)
+                .getRegionIndex();
 
-        positions.add(hiddenColumns.get(0)[0]);
-        for (int i = 1; i < hiddenColumns.size(); ++i)
+        if (left && index > 0)
         {
-
-          int result = 0;
-          if (hiddenColumns != null)
+          int[] region = hiddenColumns.get(index - 1);
+          return region[1];
+        }
+        else if (!left && index < hiddenColumns.size())
+        {
+          int[] region = hiddenColumns.get(index);
+          if (alPos < region[0])
           {
-            int index = 0;
-            int gaps = 0;
-            do
-            {
-              int[] region = hiddenColumns.get(index);
-              gaps += region[1] + 1 - region[0];
-              result = region[1] + 1;
-              index++;
-            } while (index <= i);
-
-            result -= gaps;
+            return region[0];
+          }
+          else if ((alPos <= region[1])
+                  && (index + 1 < hiddenColumns.size()))
+          {
+            // alPos is within a hidden region, return the next one
+            // if there is one
+            region = hiddenColumns.get(index + 1);
+            return region[0];
           }
-          positions.add(result);
         }
       }
-      else
-      {
-        positions = new ArrayList<>();
-      }
-
-      return positions;
+      return alPos;
     } finally
     {
       LOCK.readLock().unlock();
@@ -388,69 +670,54 @@ public class HiddenColumns
   }
 
   /**
-   * This method returns the rightmost limit of a region of an alignment with
-   * hidden columns. In otherwords, the next hidden column.
+   * Answers if a column in the alignment is visible
    * 
-   * @param index
-   *          int
+   * @param column
+   *          absolute position of column in the alignment
+   * @return true if column is visible
    */
-  public int getHiddenBoundaryRight(int alPos)
+  public boolean isVisible(int column)
   {
     try
     {
       LOCK.readLock().lock();
-      if (hiddenColumns != null)
+
+      if (!hiddenColumns.isEmpty())
       {
-        int index = 0;
-        do
+        int regionindex = cursor.findRegionForColumn(column, false)
+                .getRegionIndex();
+        if (regionindex > -1 && regionindex < hiddenColumns.size())
         {
-          int[] region = hiddenColumns.get(index);
-          if (alPos < region[0])
+          int[] region = hiddenColumns.get(regionindex);
+          // already know that column <= region[1] as cursor returns containing
+          // region or region to right
+          if (column >= region[0])
           {
-            return region[0];
+            return false;
           }
-
-          index++;
-        } while (index < hiddenColumns.size());
+        }
       }
+      return true;
 
-      return alPos;
     } finally
     {
       LOCK.readLock().unlock();
     }
-
   }
 
   /**
-   * This method returns the leftmost limit of a region of an alignment with
-   * hidden columns. In otherwords, the previous hidden column.
    * 
-   * @param index
-   *          int
+   * @return true if there are columns hidden
    */
-  public int getHiddenBoundaryLeft(int alPos)
+  public boolean hasHiddenColumns()
   {
     try
     {
       LOCK.readLock().lock();
 
-      if (hiddenColumns != null)
-      {
-        int index = hiddenColumns.size() - 1;
-        do
-        {
-          int[] region = hiddenColumns.get(index);
-          if (alPos > region[1])
-          {
-            return region[1];
-          }
-
-          index--;
-        } while (index > -1);
-      }
-
-      return alPos;
+      // we don't use getSize()>0 here because it has to iterate over
+      // the full hiddenColumns collection and so will be much slower
+      return (!hiddenColumns.isEmpty());
     } finally
     {
       LOCK.readLock().unlock();
@@ -458,258 +725,169 @@ public class HiddenColumns
   }
 
   /**
-   * This method returns the index of the hidden region to the left of a column
-   * position. If the column is in a hidden region it returns the index of the
-   * region to the left. If there is no hidden region to the left it returns -1.
    * 
-   * @param pos
-   *          int
+   * @return true if there is more than one hidden column region
    */
-  private int getHiddenIndexLeft(int pos)
+  public boolean hasMultiHiddenColumnRegions()
   {
     try
     {
-
       LOCK.readLock().lock();
-      if (hiddenColumns != null)
-      {
-        int index = hiddenColumns.size() - 1;
-        do
-        {
-          int[] region = hiddenColumns.get(index);
-          if (pos > region[1])
-          {
-            return index;
-          }
-
-          index--;
-        } while (index > -1);
-      }
-
-      return -1;
+      return !hiddenColumns.isEmpty() && hiddenColumns.size() > 1;
     } finally
     {
       LOCK.readLock().unlock();
     }
-
   }
 
+
   /**
-   * Adds the specified column range to the hidden columns
-   * 
-   * @param start
-   * @param end
+   * Returns a hashCode built from hidden column ranges
    */
-  public void hideColumns(int start, int end)
+  @Override
+  public int hashCode()
   {
-    boolean wasAlreadyLocked = false;
     try
     {
-      // check if the write lock was already locked by this thread,
-      // as this method can be called internally in loops within HiddenColumns
-      if (!LOCK.isWriteLockedByCurrentThread())
-      {
-        LOCK.writeLock().lock();
-      }
-      else
-      {
-        wasAlreadyLocked = true;
-      }
-
-      if (hiddenColumns == null)
-      {
-        hiddenColumns = new ArrayList<>();
-      }
+      LOCK.readLock().lock();
+      int hashCode = 1;
 
-      /*
-       * traverse existing hidden ranges and insert / amend / append as
-       * appropriate
-       */
-      for (int i = 0; i < hiddenColumns.size(); i++)
+      for (int[] hidden : hiddenColumns)
       {
-        int[] region = hiddenColumns.get(i);
-
-        if (end < region[0] - 1)
-        {
-          /*
-           * insert discontiguous preceding range
-           */
-          hiddenColumns.add(i, new int[] { start, end });
-          return;
-        }
-
-        if (end <= region[1])
-        {
-          /*
-           * new range overlaps existing, or is contiguous preceding it - adjust
-           * start column
-           */
-          region[0] = Math.min(region[0], start);
-          return;
-        }
-
-        if (start <= region[1] + 1)
-        {
-          /*
-           * new range overlaps existing, or is contiguous following it - adjust
-           * start and end columns
-           */
-          region[0] = Math.min(region[0], start);
-          region[1] = Math.max(region[1], end);
-
-          /*
-           * also update or remove any subsequent ranges 
-           * that are overlapped
-           */
-          while (i < hiddenColumns.size() - 1)
-          {
-            int[] nextRegion = hiddenColumns.get(i + 1);
-            if (nextRegion[0] > end + 1)
-            {
-              /*
-               * gap to next hidden range - no more to update
-               */
-              break;
-            }
-            region[1] = Math.max(nextRegion[1], end);
-            hiddenColumns.remove(i + 1);
-          }
-          return;
-        }
+        hashCode = HASH_MULTIPLIER * hashCode + hidden[0];
+        hashCode = HASH_MULTIPLIER * hashCode + hidden[1];
       }
-
-      /*
-       * remaining case is that the new range follows everything else
-       */
-      hiddenColumns.add(new int[] { start, end });
+      return hashCode;
     } finally
     {
-      if (!wasAlreadyLocked)
-      {
-        LOCK.writeLock().unlock();
-      }
+      LOCK.readLock().unlock();
     }
   }
 
-  public boolean isVisible(int column)
+  /**
+   * Hide columns corresponding to the marked bits
+   * 
+   * @param inserts
+   *          - columns mapped to bits starting from zero
+   */
+  public void hideColumns(BitSet inserts)
+  {
+    hideColumns(inserts, 0, inserts.length() - 1);
+  }
+
+  /**
+   * Hide columns corresponding to the marked bits, within the range
+   * [start,end]. Entries in tohide which are outside [start,end] are ignored.
+   * 
+   * @param tohide
+   *          columns mapped to bits starting from zero
+   * @param start
+   *          start of range to hide columns within
+   * @param end
+   *          end of range to hide columns within
+   */
+  private void hideColumns(BitSet tohide, int start, int end)
   {
     try
     {
-      LOCK.readLock().lock();
-
-      if (hiddenColumns != null)
+      LOCK.writeLock().lock();
+      for (int firstSet = tohide
+              .nextSetBit(start), lastSet = start; firstSet >= start
+                      && lastSet <= end; firstSet = tohide
+                      .nextSetBit(lastSet))
       {
-        for (int[] region : hiddenColumns)
+        lastSet = tohide.nextClearBit(firstSet);
+        if (lastSet <= end)
         {
-          if (column >= region[0] && column <= region[1])
-          {
-            return false;
-          }
+          hideColumns(firstSet, lastSet - 1);
+        }
+        else if (firstSet <= end)
+        {
+          hideColumns(firstSet, end);
         }
       }
-
-      return true;
+      cursor = new HiddenColumnsCursor(hiddenColumns);
     } finally
     {
-      LOCK.readLock().unlock();
-    }
-  }
-
-  private ArrayList<int[]> copyHiddenRegionsToArrayList()
-  {
-    int size = 0;
-    if (hiddenColumns != null)
-    {
-      size = hiddenColumns.size();
-    }
-    ArrayList<int[]> copy = new ArrayList<>(size);
-
-    for (int i = 0, j = size; i < j; i++)
-    {
-      int[] rh;
-      int[] cp;
-      rh = hiddenColumns.get(i);
-      if (rh != null)
-      {
-        cp = new int[rh.length];
-        System.arraycopy(rh, 0, cp, 0, rh.length);
-        copy.add(cp);
-      }
+      LOCK.writeLock().unlock();
     }
-
-    return copy;
   }
 
   /**
-   * Returns a copy of the vector of hidden regions, as an ArrayList. Before
-   * using this method please consider if you really need access to the hidden
-   * regions - a new (or existing!) method on HiddenColumns might be more
-   * appropriate.
+   * Hide columns corresponding to the marked bits, within the range
+   * [start,end]. Entries in tohide which are outside [start,end] are ignored.
+   * NB Existing entries in [start,end] are cleared.
    * 
-   * @return hidden regions as an ArrayList of [start,end] pairs
+   * @param tohide
+   *          columns mapped to bits starting from zero
+   * @param start
+   *          start of range to hide columns within
+   * @param end
+   *          end of range to hide columns within
    */
-  public ArrayList<int[]> getHiddenColumnsCopy()
+  public void clearAndHideColumns(BitSet tohide, int start, int end)
   {
-    try
-    {
-      LOCK.readLock().lock();
-      return copyHiddenRegionsToArrayList();
-    } finally
-    {
-      LOCK.readLock().unlock();
-    }
+    clearHiddenColumnsInRange(start, end);
+    hideColumns(tohide, start, end);
   }
 
   /**
-   * propagate shift in alignment columns to column selection
+   * Make all columns in the range [start,end] visible
    * 
    * @param start
-   *          beginning of edit
-   * @param left
-   *          shift in edit (+ve for removal, or -ve for inserts)
+   *          start of range to show columns
+   * @param end
+   *          end of range to show columns
    */
-  public List<int[]> compensateForEdit(int start, int change,
-          ColumnSelection sel)
+  private void clearHiddenColumnsInRange(int start, int end)
   {
     try
     {
       LOCK.writeLock().lock();
-      List<int[]> deletedHiddenColumns = null;
-
-      if (hiddenColumns != null)
+      
+      if (!hiddenColumns.isEmpty())
       {
-        deletedHiddenColumns = new ArrayList<>();
-        int hSize = hiddenColumns.size();
-        for (int i = 0; i < hSize; i++)
+        HiddenCursorPosition pos = cursor.findRegionForColumn(start, false);
+        int index = pos.getRegionIndex();
+
+        if (index != -1 && index != hiddenColumns.size())
         {
-          int[] region = hiddenColumns.get(i);
-          if (region[0] > start && start + change > region[1])
+          // regionIndex is the region which either contains start
+          // or lies to the right of start
+          int[] region = hiddenColumns.get(index);
+          if (region[0] < start && region[1] >= start)
           {
-            deletedHiddenColumns.add(region);
-
-            hiddenColumns.remove(i);
-            i--;
-            hSize--;
-            continue;
+            // region contains start, truncate so that it ends just before start
+            numColumns -= region[1] - start + 1;
+            region[1] = start - 1;
+            index++;
           }
 
-          if (region[0] > start)
+          int endi = index;
+          while (endi < hiddenColumns.size())
           {
-            region[0] -= change;
-            region[1] -= change;
-          }
+            region = hiddenColumns.get(endi);
 
-          if (region[0] < 0)
-          {
-            region[0] = 0;
+            if (region[1] > end)
+            {
+              if (region[0] <= end)
+              {
+                // region contains end, truncate so it starts just after end
+                numColumns -= end - region[0] + 1;
+                region[0] = end + 1;
+              }
+              break;
+            }
+
+            numColumns -= region[1] - region[0] + 1;
+            endi++;
           }
+          hiddenColumns.subList(index, endi).clear();
 
         }
 
-        this.revealHiddenColumns(0, sel);
+        cursor = new HiddenColumnsCursor(hiddenColumns);
       }
-
-      return deletedHiddenColumns;
     } finally
     {
       LOCK.writeLock().unlock();
@@ -717,47 +895,24 @@ public class HiddenColumns
   }
 
   /**
-   * propagate shift in alignment columns to column selection special version of
-   * compensateForEdit - allowing for edits within hidden regions
    * 
-   * @param start
-   *          beginning of edit
-   * @param left
-   *          shift in edit (+ve for removal, or -ve for inserts)
+   * @param updates
+   *          BitSet where hidden columns will be marked
    */
-  public void compensateForDelEdits(int start, int change)
+  protected void andNot(BitSet updates)
   {
     try
     {
       LOCK.writeLock().lock();
-      if (hiddenColumns != null)
-      {
-        for (int i = 0; i < hiddenColumns.size(); i++)
-        {
-          int[] region = hiddenColumns.get(i);
-          if (region[0] >= start)
-          {
-            region[0] -= change;
-          }
-          if (region[1] >= start)
-          {
-            region[1] -= change;
-          }
-          if (region[1] < region[0])
-          {
-            hiddenColumns.remove(i--);
-          }
 
-          if (region[0] < 0)
-          {
-            region[0] = 0;
-          }
-          if (region[1] < 0)
-          {
-            region[1] = 0;
-          }
-        }
+      BitSet hiddenBitSet = new BitSet();
+      for (int[] range : hiddenColumns)
+      {
+        hiddenBitSet.set(range[0], range[1] + 1);
       }
+      hiddenBitSet.andNot(updates);
+      hiddenColumns.clear();
+      hideColumns(hiddenBitSet);
     } finally
     {
       LOCK.writeLock().unlock();
@@ -765,135 +920,80 @@ public class HiddenColumns
   }
 
   /**
-   * return all visible segments between the given start and end boundaries
+   * Calculate the visible start and end index of an alignment.
    * 
-   * @param start
-   *          (first column inclusive from 0)
-   * @param end
-   *          (last column - not inclusive)
-   * @return int[] {i_start, i_end, ..} where intervals lie in
-   *         start<=i_start<=i_end<end
+   * @param width
+   *          full alignment width
+   * @return integer array where: int[0] = startIndex, and int[1] = endIndex
    */
-  public int[] getVisibleContigs(int start, int end)
+  public int[] getVisibleStartAndEndIndex(int width)
   {
     try
     {
       LOCK.readLock().lock();
-      if (hiddenColumns != null && hiddenColumns.size() > 0)
-      {
-        List<int[]> visiblecontigs = new ArrayList<>();
-        List<int[]> regions = getHiddenRegions();
 
-        int vstart = start;
-        int[] region;
-        int hideStart;
-        int hideEnd;
+      int firstVisible = 0;
+      int lastVisible = width - 1;
 
-        for (int j = 0; vstart < end && j < regions.size(); j++)
-        {
-          region = regions.get(j);
-          hideStart = region[0];
-          hideEnd = region[1];
-
-          if (hideEnd < vstart)
-          {
-            continue;
-          }
-          if (hideStart > vstart)
-          {
-            visiblecontigs.add(new int[] { vstart, hideStart - 1 });
-          }
-          vstart = hideEnd + 1;
-        }
+      if (!hiddenColumns.isEmpty())
+      {
+        // first visible col with index 0, convert to absolute index
+        firstVisible = visibleToAbsoluteColumn(0);
 
-        if (vstart < end)
+        // last visible column is either immediately to left of
+        // last hidden region, or is just the last column in the alignment
+        int[] lastregion = hiddenColumns.get(hiddenColumns.size() - 1);
+        if (lastregion[1] == width - 1)
         {
-          visiblecontigs.add(new int[] { vstart, end - 1 });
+          // last region is at very end of alignment
+          // last visible column immediately precedes it
+          lastVisible = lastregion[0] - 1;
         }
-        int[] vcontigs = new int[visiblecontigs.size() * 2];
-        for (int i = 0, j = visiblecontigs.size(); i < j; i++)
-        {
-          int[] vc = visiblecontigs.get(i);
-          visiblecontigs.set(i, null);
-          vcontigs[i * 2] = vc[0];
-          vcontigs[i * 2 + 1] = vc[1];
-        }
-        visiblecontigs.clear();
-        return vcontigs;
-      }
-      else
-      {
-        return new int[] { start, end - 1 };
       }
+      return new int[] { firstVisible, lastVisible };
+
     } finally
     {
       LOCK.readLock().unlock();
     }
   }
 
-  public String[] getVisibleSequenceStrings(int start, int end,
-          SequenceI[] seqs)
+  /**
+   * Finds the hidden region (if any) which starts or ends at res
+   * 
+   * @param res
+   *          visible residue position, unadjusted for hidden columns
+   * @return region as [start,end] or null if no matching region is found. If
+   *         res is adjacent to two regions, returns the left region.
+   */
+  public int[] getRegionWithEdgeAtRes(int res)
   {
     try
     {
       LOCK.readLock().lock();
-      int iSize = seqs.length;
-      String[] selections = new String[iSize];
-      if (hiddenColumns != null && hiddenColumns.size() > 0)
-      {
-        for (int i = 0; i < iSize; i++)
-        {
-          StringBuffer visibleSeq = new StringBuffer();
-          List<int[]> regions = getHiddenRegions();
-
-          int blockStart = start;
-          int blockEnd = end;
-          int[] region;
-          int hideStart;
-          int hideEnd;
+      int adjres = visibleToAbsoluteColumn(res);
 
-          for (int j = 0; j < regions.size(); j++)
-          {
-            region = regions.get(j);
-            hideStart = region[0];
-            hideEnd = region[1];
-
-            if (hideStart < start)
-            {
-              continue;
-            }
-
-            blockStart = Math.min(blockStart, hideEnd + 1);
-            blockEnd = Math.min(blockEnd, hideStart);
-
-            if (blockStart > blockEnd)
-            {
-              break;
-            }
-
-            visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
-
-            blockStart = hideEnd + 1;
-            blockEnd = end;
-          }
-
-          if (end > blockStart)
-          {
-            visibleSeq.append(seqs[i].getSequence(blockStart, end));
-          }
+      int[] reveal = null;
 
-          selections[i] = visibleSeq.toString();
-        }
-      }
-      else
+      if (!hiddenColumns.isEmpty())
       {
-        for (int i = 0; i < iSize; i++)
+        // look for a region ending just before adjres
+        int regionindex = cursor.findRegionForColumn(adjres - 1, false)
+                .getRegionIndex();
+        if (regionindex < hiddenColumns.size()
+                && hiddenColumns.get(regionindex)[1] == adjres - 1)
+        {
+          reveal = hiddenColumns.get(regionindex);
+        }
+        // check if the region ends just after adjres
+        else if (regionindex < hiddenColumns.size()
+                && hiddenColumns.get(regionindex)[0] == adjres + 1)
         {
-          selections[i] = seqs[i].getSequenceAsString(start, end);
+          reveal = hiddenColumns.get(regionindex);
         }
       }
+      return reveal;
 
-      return selections;
     } finally
     {
       LOCK.readLock().unlock();
@@ -901,92 +1001,14 @@ public class HiddenColumns
   }
 
   /**
-   * Locate the first and last position visible for this sequence. if seq isn't
-   * visible then return the position of the left and right of the hidden
-   * boundary region, and the corresponding alignment column indices for the
-   * extent of the sequence
-   * 
-   * @param seq
-   * @return int[] { visible start, visible end, first seqpos, last seqpos,
-   *         alignment index for seq start, alignment index for seq end }
+   * Return an iterator over the hidden regions
    */
-  public int[] locateVisibleBoundsOfSequence(SequenceI seq)
+  public Iterator<int[]> iterator()
   {
     try
     {
       LOCK.readLock().lock();
-      int fpos = seq.getStart();
-      int lpos = seq.getEnd();
-      int start = 0;
-
-      if (hiddenColumns == null || hiddenColumns.size() == 0)
-      {
-        int ifpos = seq.findIndex(fpos) - 1;
-        int ilpos = seq.findIndex(lpos) - 1;
-        return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
-      }
-
-      // Simply walk along the sequence whilst watching for hidden column
-      // boundaries
-      List<int[]> regions = getHiddenRegions();
-      int spos = fpos;
-      int lastvispos = -1;
-      int rcount = 0;
-      int hideStart = seq.getLength();
-      int hideEnd = -1;
-      int visPrev = 0;
-      int visNext = 0;
-      int firstP = -1;
-      int lastP = -1;
-      boolean foundStart = false;
-      for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
-              && p < pLen; p++)
-      {
-        if (!Comparison.isGap(seq.getCharAt(p)))
-        {
-          // keep track of first/last column
-          // containing sequence data regardless of visibility
-          if (firstP == -1)
-          {
-            firstP = p;
-          }
-          lastP = p;
-          // update hidden region start/end
-          while (hideEnd < p && rcount < regions.size())
-          {
-            int[] region = regions.get(rcount++);
-            visPrev = visNext;
-            visNext += region[0] - visPrev;
-            hideStart = region[0];
-            hideEnd = region[1];
-          }
-          if (hideEnd < p)
-          {
-            hideStart = seq.getLength();
-          }
-          // update visible boundary for sequence
-          if (p < hideStart)
-          {
-            if (!foundStart)
-            {
-              fpos = spos;
-              start = p;
-              foundStart = true;
-            }
-            lastvispos = p;
-            lpos = spos;
-          }
-          // look for next sequence position
-          spos++;
-        }
-      }
-      if (foundStart)
-      {
-        return new int[] { findColumnPosition(start),
-            findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
-      }
-      // otherwise, sequence was completely hidden
-      return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
+      return new RangeIterator(hiddenColumns);
     } finally
     {
       LOCK.readLock().unlock();
@@ -994,120 +1016,54 @@ public class HiddenColumns
   }
 
   /**
-   * delete any columns in alignmentAnnotation that are hidden (including
-   * sequence associated annotation).
+   * Return a bounded iterator over the hidden regions
    * 
-   * @param alignmentAnnotation
+   * @param start
+   *          position to start from (inclusive, absolute column position)
+   * @param end
+   *          position to end at (inclusive, absolute column position)
+   * @return
    */
-  public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
+  public Iterator<int[]> getBoundedIterator(int start, int end)
   {
-    makeVisibleAnnotation(-1, -1, alignmentAnnotation);
+    try
+    {
+      LOCK.readLock().lock();
+      return new RangeIterator(start, end, hiddenColumns);
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
   }
 
   /**
-   * delete any columns in alignmentAnnotation that are hidden (including
-   * sequence associated annotation).
+   * Return a bounded iterator over the *visible* start positions of hidden
+   * regions
    * 
    * @param start
-   *          remove any annotation to the right of this column
+   *          position to start from (inclusive, visible column position)
    * @param end
-   *          remove any annotation to the left of this column
-   * @param alignmentAnnotation
-   *          the annotation to operate on
+   *          position to end at (inclusive, visible column position)
    */
-  public void makeVisibleAnnotation(int start, int end,
-          AlignmentAnnotation alignmentAnnotation)
+  public Iterator<Integer> getStartRegionIterator(int start, int end)
   {
     try
     {
       LOCK.readLock().lock();
-      if (alignmentAnnotation.annotations == null)
-      {
-        return;
-      }
-      if (start == end && end == -1)
-      {
-        start = 0;
-        end = alignmentAnnotation.annotations.length;
-      }
-      if (hiddenColumns != null && hiddenColumns.size() > 0)
-      {
-        // then mangle the alignmentAnnotation annotation array
-        Vector<Annotation[]> annels = new Vector<>();
-        Annotation[] els = null;
-        List<int[]> regions = getHiddenRegions();
-        int blockStart = start;
-        int blockEnd = end;
-        int[] region;
-        int hideStart;
-        int hideEnd;
-        int w = 0;
-
-        for (int j = 0; j < regions.size(); j++)
-        {
-          region = regions.get(j);
-          hideStart = region[0];
-          hideEnd = region[1];
-
-          if (hideStart < start)
-          {
-            continue;
-          }
-
-          blockStart = Math.min(blockStart, hideEnd + 1);
-          blockEnd = Math.min(blockEnd, hideStart);
-
-          if (blockStart > blockEnd)
-          {
-            break;
-          }
-
-          annels.addElement(els = new Annotation[blockEnd - blockStart]);
-          System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
-                  0, els.length);
-          w += els.length;
-          blockStart = hideEnd + 1;
-          blockEnd = end;
-        }
 
-        if (end > blockStart)
-        {
-          annels.addElement(els = new Annotation[end - blockStart + 1]);
-          if ((els.length
-                  + blockStart) <= alignmentAnnotation.annotations.length)
-          {
-            // copy just the visible segment of the annotation row
-            System.arraycopy(alignmentAnnotation.annotations, blockStart,
-                    els, 0, els.length);
-          }
-          else
-          {
-            // copy to the end of the annotation row
-            System.arraycopy(alignmentAnnotation.annotations, blockStart,
-                    els, 0,
-                    (alignmentAnnotation.annotations.length - blockStart));
-          }
-          w += els.length;
-        }
-        if (w == 0)
-        {
-          return;
-        }
+      // get absolute position of column in alignment
+      int absoluteStart = visibleToAbsoluteColumn(start);
 
-        alignmentAnnotation.annotations = new Annotation[w];
-        w = 0;
+      // Get cursor position and supply it to the iterator:
+      // Since we want visible region start, we look for a cursor for the
+      // (absoluteStart-1), then if absoluteStart is the start of a visible
+      // region we'll get the cursor pointing to the region before, which is
+      // what we want
+      HiddenCursorPosition pos = cursor
+              .findRegionForColumn(absoluteStart - 1, false);
 
-        for (Annotation[] chnk : annels)
-        {
-          System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
-                  chnk.length);
-          w += chnk.length;
-        }
-      }
-      else
-      {
-        alignmentAnnotation.restrict(start, end);
-      }
+      return new StartRegionIterator(pos, start, end,
+              hiddenColumns);
     } finally
     {
       LOCK.readLock().unlock();
@@ -1115,15 +1071,21 @@ public class HiddenColumns
   }
 
   /**
+   * Return an iterator over visible *columns* (not regions) between the given
+   * start and end boundaries
    * 
-   * @return true if there are columns hidden
+   * @param start
+   *          first column (inclusive)
+   * @param end
+   *          last column (inclusive)
    */
-  public boolean hasHiddenColumns()
+  public Iterator<Integer> getVisibleColsIterator(int start, int end)
   {
     try
     {
       LOCK.readLock().lock();
-      return hiddenColumns != null && hiddenColumns.size() > 0;
+      return new RangeElementsIterator(
+              new VisibleContigsIterator(start, end + 1, hiddenColumns));
     } finally
     {
       LOCK.readLock().unlock();
@@ -1131,577 +1093,36 @@ public class HiddenColumns
   }
 
   /**
+   * return an iterator over visible segments between the given start and end
+   * boundaries
    * 
-   * @return true if there are more than one set of columns hidden
+   * @param start
+   *          first column, inclusive from 0
+   * @param end
+   *          last column - not inclusive
+   * @param useVisibleCoords
+   *          if true, start and end are visible column positions, not absolute
+   *          positions*
    */
-  public boolean hasManyHiddenColumns()
+  public VisibleContigsIterator getVisContigsIterator(int start,
+          int end,
+          boolean useVisibleCoords)
   {
+    int adjstart = start;
+    int adjend = end;
+    if (useVisibleCoords)
+    {
+      adjstart = visibleToAbsoluteColumn(start);
+      adjend = visibleToAbsoluteColumn(end);
+    }
+
     try
     {
       LOCK.readLock().lock();
-      return hiddenColumns != null && hiddenColumns.size() > 1;
+      return new VisibleContigsIterator(adjstart, adjend, hiddenColumns);
     } finally
     {
       LOCK.readLock().unlock();
     }
   }
-
-  /**
-   * mark the columns corresponding to gap characters as hidden in the column
-   * selection
-   * 
-   * @param sr
-   */
-  public void hideInsertionsFor(SequenceI sr)
-  {
-    try
-    {
-      LOCK.writeLock().lock();
-      List<int[]> inserts = sr.getInsertions();
-      for (int[] r : inserts)
-      {
-        hideColumns(r[0], r[1]);
-      }
-    } finally
-    {
-      LOCK.writeLock().unlock();
-    }
-  }
-
-  /**
-   * Unhides, and adds to the selection list, all hidden columns
-   */
-  public void revealAllHiddenColumns(ColumnSelection sel)
-  {
-    try
-    {
-      LOCK.writeLock().lock();
-      if (hiddenColumns != null)
-      {
-        for (int i = 0; i < hiddenColumns.size(); i++)
-        {
-          int[] region = hiddenColumns.get(i);
-          for (int j = region[0]; j < region[1] + 1; j++)
-          {
-            sel.addElement(j);
-          }
-        }
-      }
-
-      hiddenColumns = null;
-    } finally
-    {
-      LOCK.writeLock().unlock();
-    }
-  }
-
-  /**
-   * Reveals, and marks as selected, the hidden column range with the given
-   * start column
-   * 
-   * @param start
-   */
-  public void revealHiddenColumns(int start, ColumnSelection sel)
-  {
-    try
-    {
-      LOCK.writeLock().lock();
-      for (int i = 0; i < hiddenColumns.size(); i++)
-      {
-        int[] region = hiddenColumns.get(i);
-        if (start == region[0])
-        {
-          for (int j = region[0]; j < region[1] + 1; j++)
-          {
-            sel.addElement(j);
-          }
-
-          hiddenColumns.remove(region);
-          break;
-        }
-      }
-      if (hiddenColumns.size() == 0)
-      {
-        hiddenColumns = null;
-      }
-    } finally
-    {
-      LOCK.writeLock().unlock();
-    }
-  }
-
-  /**
-   * removes intersection of position,length ranges in deletions from the
-   * start,end regions marked in intervals.
-   * 
-   * @param shifts
-   * @param intervals
-   * @return
-   */
-  private boolean pruneIntervalList(final List<int[]> shifts,
-          ArrayList<int[]> intervals)
-  {
-    boolean pruned = false;
-    int i = 0;
-    int j = intervals.size() - 1;
-    int s = 0;
-    int t = shifts.size() - 1;
-    int[] hr = intervals.get(i);
-    int[] sr = shifts.get(s);
-    while (i <= j && s <= t)
-    {
-      boolean trailinghn = hr[1] >= sr[0];
-      if (!trailinghn)
-      {
-        if (i < j)
-        {
-          hr = intervals.get(++i);
-        }
-        else
-        {
-          i++;
-        }
-        continue;
-      }
-      int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
-      if (endshift < hr[0] || endshift < sr[0])
-      { // leadinghc disjoint or not a deletion
-        if (s < t)
-        {
-          sr = shifts.get(++s);
-        }
-        else
-        {
-          s++;
-        }
-        continue;
-      }
-      boolean leadinghn = hr[0] >= sr[0];
-      boolean leadinghc = hr[0] < endshift;
-      boolean trailinghc = hr[1] < endshift;
-      if (leadinghn)
-      {
-        if (trailinghc)
-        { // deleted hidden region.
-          intervals.remove(i);
-          pruned = true;
-          j--;
-          if (i <= j)
-          {
-            hr = intervals.get(i);
-          }
-          continue;
-        }
-        if (leadinghc)
-        {
-          hr[0] = endshift; // clip c terminal region
-          leadinghn = !leadinghn;
-          pruned = true;
-        }
-      }
-      if (!leadinghn)
-      {
-        if (trailinghc)
-        {
-          if (trailinghn)
-          {
-            hr[1] = sr[0] - 1;
-            pruned = true;
-          }
-        }
-        else
-        {
-          // sr contained in hr
-          if (s < t)
-          {
-            sr = shifts.get(++s);
-          }
-          else
-          {
-            s++;
-          }
-          continue;
-        }
-      }
-    }
-    return pruned; // true if any interval was removed or modified by
-    // operations.
-  }
-
-  /**
-   * remove any hiddenColumns or selected columns and shift remaining based on a
-   * series of position, range deletions.
-   * 
-   * @param deletions
-   */
-  public void pruneDeletions(List<int[]> shifts)
-  {
-    try
-    {
-      LOCK.writeLock().lock();
-      // delete any intervals intersecting.
-      if (hiddenColumns != null)
-      {
-        pruneIntervalList(shifts, hiddenColumns);
-        if (hiddenColumns != null && hiddenColumns.size() == 0)
-        {
-          hiddenColumns = null;
-        }
-      }
-    } finally
-    {
-      LOCK.writeLock().unlock();
-    }
-  }
-
-  /**
-   * Add gaps into the sequences aligned to profileseq under the given
-   * AlignmentView
-   * 
-   * @param profileseq
-   * @param al
-   *          - alignment to have gaps inserted into it
-   * @param input
-   *          - alignment view where sequence corresponding to profileseq is
-   *          first entry
-   * @return new HiddenColumns for new alignment view, with insertions into
-   *         profileseq marked as hidden.
-   */
-  public static HiddenColumns propagateInsertions(SequenceI profileseq,
-          AlignmentI al, AlignmentView input)
-  {
-    int profsqpos = 0;
-
-    char gc = al.getGapCharacter();
-    Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
-    HiddenColumns nview = (HiddenColumns) alandhidden[1];
-    SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
-    nview.propagateInsertions(profileseq, al, origseq);
-    return nview;
-  }
-
-  /**
-   * 
-   * @param profileseq
-   *          - sequence in al which corresponds to origseq
-   * @param al
-   *          - alignment which is to have gaps inserted into it
-   * @param origseq
-   *          - sequence corresponding to profileseq which defines gap map for
-   *          modifying al
-   */
-  private void propagateInsertions(SequenceI profileseq, AlignmentI al,
-          SequenceI origseq)
-  {
-    char gc = al.getGapCharacter();
-    // recover mapping between sequence's non-gap positions and positions
-    // mapping to view.
-    pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
-    int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
-    int spos = 0;
-    int offset = 0;
-
-    // add profile to visible contigs
-    for (int v = 0; v < viscontigs.length; v += 2)
-    {
-      if (viscontigs[v] > spos)
-      {
-        StringBuffer sb = new StringBuffer();
-        for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
-        {
-          sb.append(gc);
-        }
-        for (int s = 0, ns = al.getHeight(); s < ns; s++)
-        {
-          SequenceI sqobj = al.getSequenceAt(s);
-          if (sqobj != profileseq)
-          {
-            String sq = al.getSequenceAt(s).getSequenceAsString();
-            if (sq.length() <= spos + offset)
-            {
-              // pad sequence
-              int diff = spos + offset - sq.length() - 1;
-              if (diff > 0)
-              {
-                // pad gaps
-                sq = sq + sb;
-                while ((diff = spos + offset - sq.length() - 1) > 0)
-                {
-                  // sq = sq
-                  // + ((diff >= sb.length()) ? sb.toString() : sb
-                  // .substring(0, diff));
-                  if (diff >= sb.length())
-                  {
-                    sq += sb.toString();
-                  }
-                  else
-                  {
-                    char[] buf = new char[diff];
-                    sb.getChars(0, diff, buf, 0);
-                    sq += buf.toString();
-                  }
-                }
-              }
-              sq += sb.toString();
-            }
-            else
-            {
-              al.getSequenceAt(s).setSequence(sq.substring(0, spos + offset)
-                      + sb.toString() + sq.substring(spos + offset));
-            }
-          }
-        }
-        // offset+=sb.length();
-      }
-      spos = viscontigs[v + 1] + 1;
-    }
-    if ((offset + spos) < profileseq.getLength())
-    {
-      // pad the final region with gaps.
-      StringBuffer sb = new StringBuffer();
-      for (int s = 0, ns = profileseq.getLength() - spos
-              - offset; s < ns; s++)
-      {
-        sb.append(gc);
-      }
-      for (int s = 0, ns = al.getHeight(); s < ns; s++)
-      {
-        SequenceI sqobj = al.getSequenceAt(s);
-        if (sqobj == profileseq)
-        {
-          continue;
-        }
-        String sq = sqobj.getSequenceAsString();
-        // pad sequence
-        int diff = origseq.getLength() - sq.length();
-        while (diff > 0)
-        {
-          // sq = sq
-          // + ((diff >= sb.length()) ? sb.toString() : sb
-          // .substring(0, diff));
-          if (diff >= sb.length())
-          {
-            sq += sb.toString();
-          }
-          else
-          {
-            char[] buf = new char[diff];
-            sb.getChars(0, diff, buf, 0);
-            sq += buf.toString();
-          }
-          diff = origseq.getLength() - sq.length();
-        }
-      }
-    }
-  }
-
-  /**
-   * remove any hiddenColumns or selected columns and shift remaining based on a
-   * series of position, range deletions.
-   * 
-   * @param deletions
-   */
-  private void pruneDeletions(ShiftList deletions)
-  {
-    if (deletions != null)
-    {
-      final List<int[]> shifts = deletions.getShifts();
-      if (shifts != null && shifts.size() > 0)
-      {
-        pruneDeletions(shifts);
-
-        // and shift the rest.
-        this.compensateForEdits(deletions);
-      }
-    }
-  }
-
-  /**
-   * Adjust hidden column boundaries based on a series of column additions or
-   * deletions in visible regions.
-   * 
-   * @param shiftrecord
-   * @return
-   */
-  private ShiftList compensateForEdits(ShiftList shiftrecord)
-  {
-    if (shiftrecord != null)
-    {
-      final List<int[]> shifts = shiftrecord.getShifts();
-      if (shifts != null && shifts.size() > 0)
-      {
-        int shifted = 0;
-        for (int i = 0, j = shifts.size(); i < j; i++)
-        {
-          int[] sh = shifts.get(i);
-          compensateForDelEdits(shifted + sh[0], sh[1]);
-          shifted -= sh[1];
-        }
-      }
-      return shiftrecord.getInverse();
-    }
-    return null;
-  }
-
-  /**
-   * Returns a hashCode built from hidden column ranges
-   */
-  @Override
-  public int hashCode()
-  {
-    try
-    {
-      LOCK.readLock().lock();
-      int hashCode = 1;
-      if (hiddenColumns != null)
-      {
-        for (int[] hidden : hiddenColumns)
-        {
-          hashCode = 31 * hashCode + hidden[0];
-          hashCode = 31 * hashCode + hidden[1];
-        }
-      }
-      return hashCode;
-    } finally
-    {
-      LOCK.readLock().unlock();
-    }
-  }
-
-  /**
-   * Hide columns corresponding to the marked bits
-   * 
-   * @param inserts
-   *          - columns map to bits starting from zero
-   */
-  public void hideMarkedBits(BitSet inserts)
-  {
-    try
-    {
-      LOCK.writeLock().lock();
-      for (int firstSet = inserts
-              .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
-                      .nextSetBit(lastSet))
-      {
-        lastSet = inserts.nextClearBit(firstSet);
-        hideColumns(firstSet, lastSet - 1);
-      }
-    } finally
-    {
-      LOCK.writeLock().unlock();
-    }
-  }
-
-  /**
-   * 
-   * @param inserts
-   *          BitSet where hidden columns will be marked
-   */
-  public void markHiddenRegions(BitSet inserts)
-  {
-    try
-    {
-      LOCK.readLock().lock();
-      if (hiddenColumns == null)
-      {
-        return;
-      }
-      for (int[] range : hiddenColumns)
-      {
-        inserts.set(range[0], range[1] + 1);
-      }
-    } finally
-    {
-      LOCK.readLock().unlock();
-    }
-  }
-
-  /**
-   * Calculate the visible start and end index of an alignment.
-   * 
-   * @param width
-   *          full alignment width
-   * @return integer array where: int[0] = startIndex, and int[1] = endIndex
-   */
-  public int[] getVisibleStartAndEndIndex(int width)
-  {
-    try
-    {
-      LOCK.readLock().lock();
-      int[] alignmentStartEnd = new int[] { 0, width - 1 };
-      int startPos = alignmentStartEnd[0];
-      int endPos = alignmentStartEnd[1];
-
-      int[] lowestRange = new int[] { -1, -1 };
-      int[] higestRange = new int[] { -1, -1 };
-
-      if (hiddenColumns == null)
-      {
-        return new int[] { startPos, endPos };
-      }
-
-      for (int[] hiddenCol : hiddenColumns)
-      {
-        lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
-        higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
-      }
-
-      if (lowestRange[0] == -1 && lowestRange[1] == -1)
-      {
-        startPos = alignmentStartEnd[0];
-      }
-      else
-      {
-        startPos = lowestRange[1] + 1;
-      }
-
-      if (higestRange[0] == -1 && higestRange[1] == -1)
-      {
-        endPos = alignmentStartEnd[1];
-      }
-      else
-      {
-        endPos = higestRange[0] - 1;
-      }
-      return new int[] { startPos, endPos };
-    } finally
-    {
-      LOCK.readLock().unlock();
-    }
-
-  }
-
-  /**
-   * Finds the hidden region (if any) which starts or ends at res
-   * 
-   * @param res
-   *          visible residue position, unadjusted for hidden columns
-   * @return region as [start,end] or null if no matching region is found
-   */
-  public int[] getRegionWithEdgeAtRes(int res)
-  {
-    try
-    {
-      LOCK.readLock().lock();
-      int adjres = adjustForHiddenColumns(res);
-
-      int[] reveal = null;
-      if (hiddenColumns != null)
-      {
-        for (int[] region : hiddenColumns)
-        {
-          if (adjres + 1 == region[0] || adjres - 1 == region[1])
-          {
-            reveal = region;
-            break;
-          }
-        }
-      }
-      return reveal;
-    } finally
-    {
-      LOCK.readLock().unlock();
-    }
-  }
-
 }
diff --git a/src/jalview/datamodel/HiddenColumnsCursor.java b/src/jalview/datamodel/HiddenColumnsCursor.java
new file mode 100644 (file)
index 0000000..2e9d798
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.datamodel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class HiddenColumnsCursor
+{
+  // absolute position of first hidden column
+  private int firstColumn;
+
+  private List<int[]> hiddenColumns = new ArrayList<>();
+
+  private HiddenCursorPosition cursorPos = new HiddenCursorPosition(0, 0);
+
+  protected HiddenColumnsCursor()
+  {
+
+  }
+
+  protected HiddenColumnsCursor(List<int[]> hiddenCols)
+  {
+    resetCursor(hiddenCols, 0, 0);
+  }
+
+  protected HiddenColumnsCursor(List<int[]> hiddenCols, int index,
+          int hiddencount)
+  {
+    resetCursor(hiddenCols, index, hiddencount);
+  }
+
+  /**
+   * Reset the cursor with a new hidden columns collection, where we know in
+   * advance the index and hidden columns count of a particular location.
+   * 
+   * @param hiddenCols
+   *          new hidden columns collection
+   * @param index
+   *          cursor index to reset to
+   * @param hiddencount
+   *          hidden columns count to reset to
+   */
+  private void resetCursor(List<int[]> hiddenCols, int index,
+          int hiddencount)
+  {
+    hiddenColumns = hiddenCols;
+    if (!hiddenCols.isEmpty())
+    {
+      firstColumn = hiddenColumns.get(0)[0];
+      cursorPos = new HiddenCursorPosition(index,
+              hiddencount);
+    }
+  }
+
+  /**
+   * Get the cursor pointing to the hidden region that column is within (if
+   * column is hidden) or which is to the right of column (if column is
+   * visible). If no hidden columns are to the right, returns a cursor pointing
+   * to an imaginary hidden region beyond the end of the hidden columns
+   * collection (this ensures the count of previous hidden columns is correct).
+   * If hidden columns is empty returns null.
+   * 
+   * @param column
+   *          index of column in visible or absolute coordinates
+   * @param useVisible
+   *          true if column is in visible coordinates, false if absolute
+   * @return cursor pointing to hidden region containing the column (if hidden)
+   *         or to the right of the column (if visible)
+   */
+  protected HiddenCursorPosition findRegionForColumn(int column,
+          boolean useVisible)
+  {
+    if (hiddenColumns.isEmpty())
+    {
+      return null;
+    }
+
+    // used to add in hiddenColumns offset when working with visible columns
+    int offset = (useVisible ? 1 : 0);
+
+    HiddenCursorPosition pos = cursorPos;
+    int index = pos.getRegionIndex();
+    int hiddenCount = pos.getHiddenSoFar();
+
+    if (column < firstColumn)
+    {
+      pos = new HiddenCursorPosition(0, 0);
+    }
+
+    // column is after current region
+    else if ((index < hiddenColumns.size())
+            && (hiddenColumns.get(index)[0] <= column
+                    + offset * hiddenCount))
+    {
+      // iterate from where we are now, if we're lucky we'll be close by
+      // (but still better than iterating from 0)
+      // stop when we find the region *before* column
+      // i.e. the next region starts after column or if not, ends after column
+      pos = searchForward(pos, column, useVisible);
+    }
+
+    // column is before current region
+    else
+    {
+      pos = searchBackward(pos, column, useVisible);
+    }
+    cursorPos = pos;
+    return pos;
+  }
+
+  /**
+   * Search forwards through the hidden columns collection to find the hidden
+   * region immediately before a column
+   * 
+   * @param pos
+   *          current position
+   * @param column
+   *          column to locate
+   * @param useVisible
+   *          whether using visible or absolute coordinates
+   * @return position of region before column
+   */
+  private HiddenCursorPosition searchForward(HiddenCursorPosition pos,
+          int column, boolean useVisible)
+  {
+    HiddenCursorPosition p = pos;
+    if (useVisible)
+    {
+      while ((p.getRegionIndex() < hiddenColumns.size())
+              && hiddenColumns.get(p.getRegionIndex())[0] <= column
+                      + p.getHiddenSoFar())
+      {
+        p = stepForward(p);
+      }
+    }
+    else
+    {
+      while ((p.getRegionIndex() < hiddenColumns.size())
+              && hiddenColumns.get(p.getRegionIndex())[1] < column)
+      {
+        p = stepForward(p);
+      }
+    }
+    return p;
+  }
+
+  /**
+   * Move to the next (rightwards) hidden region after a given cursor position
+   * 
+   * @param p
+   *          current position of cursor
+   * @return new position of cursor at next region
+   */
+  private HiddenCursorPosition stepForward(HiddenCursorPosition p)
+  {
+    int[] region = hiddenColumns.get(p.getRegionIndex());
+
+    // increment the index, and add this region's hidden columns to the hidden
+    // column count
+    return new HiddenCursorPosition(p.getRegionIndex() + 1,
+            p.getHiddenSoFar() + region[1] - region[0] + 1);
+  }
+
+  /**
+   * Search backwards through the hidden columns collection to find the hidden
+   * region immediately before (left of) a given column
+   * 
+   * @param pos
+   *          current position
+   * @param column
+   *          column to locate
+   * @param useVisible
+   *          whether using visible or absolute coordinates
+   * @return position of region immediately to left of column
+   */
+  private HiddenCursorPosition searchBackward(HiddenCursorPosition p,
+          int column, boolean useVisible)
+  {
+    int i = p.getRegionIndex();
+    int h = p.getHiddenSoFar();
+
+    // used to add in hiddenColumns offset when working with visible columns
+    int offset = (useVisible ? 1 : 0);
+
+    while ((i > 0) && (hiddenColumns.get(i - 1)[1] >= column + offset * h))
+    {
+      i--;
+      int[] region = hiddenColumns.get(i);
+      h -= region[1] - region[0] + 1;
+    }
+    return new HiddenCursorPosition(i, h);
+  }
+
+}
diff --git a/src/jalview/datamodel/HiddenCursorPosition.java b/src/jalview/datamodel/HiddenCursorPosition.java
new file mode 100644 (file)
index 0000000..160ed71
--- /dev/null
@@ -0,0 +1,26 @@
+package jalview.datamodel;
+
+public final class HiddenCursorPosition
+{
+  // index of last visited region
+  private final int regionIndex;
+
+  // number of hidden columns before last visited region
+  private final int hiddenSoFar;
+
+  public HiddenCursorPosition(int index, int hiddencount)
+  {
+    regionIndex = index;
+    hiddenSoFar = hiddencount;
+  }
+
+  public int getRegionIndex()
+  {
+    return regionIndex;
+  }
+
+  public int getHiddenSoFar()
+  {
+    return hiddenSoFar;
+  }
+}
diff --git a/src/jalview/datamodel/RangeElementsIterator.java b/src/jalview/datamodel/RangeElementsIterator.java
new file mode 100644 (file)
index 0000000..85272f2
--- /dev/null
@@ -0,0 +1,88 @@
+package jalview.datamodel;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterator over each element in a set of ranges i.e. if ranges is {[3,6],
+ * [12,15]} it will iterate over {3,4,5,6,12,13,14,15}. Uses a local copy of the
+ * set of ranges.
+ * 
+ * @author kmourao
+ *
+ */
+public class RangeElementsIterator implements Iterator<Integer>
+{
+  private int last;
+
+  private int current;
+
+  private int next;
+
+  private Iterator<int[]> rangeIterator;
+
+  private int[] nextRange = null;
+
+  RangeElementsIterator(Iterator<int[]> it)
+  {
+    rangeIterator = it;
+    if (rangeIterator.hasNext())
+    {
+      nextRange = rangeIterator.next();
+      next = nextRange[0];
+      last = nextRange[1];
+    }
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return rangeIterator.hasNext() || next <= last;
+  }
+
+  @Override
+  public Integer next()
+  {
+    if (!hasNext())
+    {
+      throw new NoSuchElementException();
+    }
+
+    current = next;
+
+    // recalculate next
+    next++;
+
+    // if there are more ranges need to check if next is in a range
+    checkNextRange();
+    return current;
+  }
+
+  /**
+   * Check how next position relates to next range, and update next position if
+   * necessary
+   */
+  private void checkNextRange()
+  {
+    if (nextRange != null && next > nextRange[1])
+    {
+      if (rangeIterator.hasNext())
+      {
+        nextRange = rangeIterator.next();
+        next = nextRange[0];
+        last = nextRange[1];
+      }
+      else
+      {
+        nextRange = null;
+      }
+
+    }
+  }
+
+  @Override
+  public void remove()
+  {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/src/jalview/datamodel/RangeIterator.java b/src/jalview/datamodel/RangeIterator.java
new file mode 100644 (file)
index 0000000..7a69ac6
--- /dev/null
@@ -0,0 +1,114 @@
+package jalview.datamodel;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An iterator which iterates over a list of ranges. Works with a copy of the
+ * collection of ranges.
+ */
+public class RangeIterator implements Iterator<int[]>
+{
+  // current index in rangeList
+  private int currentPosition = 0;
+
+  // current range in rangeList
+  private int[] currentRange;
+
+  // local copy or reference to rangeList
+  private List<int[]> localRanges;
+
+  /**
+   * Unbounded constructor
+   * 
+   * @param rangeList
+   *          list of ranges to iterate over
+   */
+  RangeIterator(List<int[]> rangeList)
+  {
+    if (!rangeList.isEmpty())
+    {
+      int last = rangeList.get(rangeList.size() - 1)[1];
+      init(0, last, rangeList);
+    }
+    else
+    {
+      init(0, 0, rangeList);
+    }
+  }
+
+  /**
+   * Construct an iterator over rangeList bounded at [lowerBound,upperBound]
+   * 
+   * @param lowerBound
+   *          lower bound to iterate from
+   * @param upperBound
+   *          upper bound to iterate to
+   * @param rangeList
+   *          list of ranges to iterate over
+   */
+  RangeIterator(int lowerBound, int upperBound,
+          List<int[]> rangeList)
+  {
+    init(lowerBound, upperBound, rangeList);
+  }
+
+  /**
+   * Construct an iterator over rangeList bounded at [lowerBound,upperBound]
+   * 
+   * @param lowerBound
+   *          lower bound to iterate from
+   * @param upperBound
+   *          upper bound to iterate to
+   */
+  private void init(int lowerBound, int upperBound,
+          List<int[]> rangeList)
+  {
+    int start = lowerBound;
+    int end = upperBound;
+
+    if (rangeList != null)
+    {
+      localRanges = new ArrayList<>();
+
+      // iterate until a range overlaps with [start,end]
+      int i = 0;
+      while ((i < rangeList.size()) && (rangeList.get(i)[1] < start))
+      {
+        i++;
+      }
+
+      // iterate from start to end, adding each range. Positions are
+      // absolute, and all ranges which *overlap* [start,end] are added.
+      while (i < rangeList.size() && (rangeList.get(i)[0] <= end))
+      {
+        int[] rh = rangeList.get(i);
+        int[] cp = new int[2];
+        System.arraycopy(rh, 0, cp, 0, rh.length);
+        localRanges.add(cp);
+        i++;
+      }
+    }
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return (localRanges != null) && (currentPosition < localRanges.size());
+  }
+
+  @Override
+  public int[] next()
+  {
+    currentRange = localRanges.get(currentPosition);
+    currentPosition++;
+    return currentRange;
+  }
+
+  @Override
+  public void remove()
+  {
+    localRanges.remove(--currentPosition);
+  }
+}
index 15d1378..abf334c 100755 (executable)
@@ -34,6 +34,7 @@ import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Enumeration;
+import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Vector;
@@ -408,7 +409,7 @@ public class Sequence extends ASequence implements SequenceI
   {
     if (pdbIds == null)
     {
-      pdbIds = new Vector<PDBEntry>();
+      pdbIds = new Vector<>();
       pdbIds.add(entry);
       return true;
     }
@@ -1071,6 +1072,27 @@ public class Sequence extends ASequence implements SequenceI
     return map;
   }
 
+  /**
+   * Build a bitset corresponding to sequence gaps
+   * 
+   * @return a BitSet where set values correspond to gaps in the sequence
+   */
+  @Override
+  public BitSet gapBitset()
+  {
+    BitSet gaps = new BitSet(sequence.length);
+    int j = 0;
+    while (j < sequence.length)
+    {
+      if (jalview.util.Comparison.isGap(sequence[j]))
+      {
+        gaps.set(j);
+      }
+      j++;
+    }
+    return gaps;
+  }
+
   @Override
   public int[] findPositionMap()
   {
@@ -1094,7 +1116,7 @@ public class Sequence extends ASequence implements SequenceI
   @Override
   public List<int[]> getInsertions()
   {
-    ArrayList<int[]> map = new ArrayList<int[]>();
+    ArrayList<int[]> map = new ArrayList<>();
     int lastj = -1, j = 0;
     int pos = start;
     int seqlen = sequence.length;
@@ -1405,7 +1427,7 @@ public class Sequence extends ASequence implements SequenceI
   {
     if (this.annotation == null)
     {
-      this.annotation = new Vector<AlignmentAnnotation>();
+      this.annotation = new Vector<>();
     }
     if (!this.annotation.contains(annotation))
     {
@@ -1572,7 +1594,7 @@ public class Sequence extends ASequence implements SequenceI
       return null;
     }
 
-    Vector<AlignmentAnnotation> subset = new Vector<AlignmentAnnotation>();
+    Vector<AlignmentAnnotation> subset = new Vector<>();
     Enumeration<AlignmentAnnotation> e = annotation.elements();
     while (e.hasMoreElements())
     {
@@ -1706,7 +1728,7 @@ public class Sequence extends ASequence implements SequenceI
   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
           String label)
   {
-    List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
+    List<AlignmentAnnotation> result = new ArrayList<>();
     if (this.annotation != null)
     {
       for (AlignmentAnnotation ann : annotation)
@@ -1762,7 +1784,7 @@ public class Sequence extends ASequence implements SequenceI
     }
     synchronized (dbrefs)
     {
-      List<DBRefEntry> primaries = new ArrayList<DBRefEntry>();
+      List<DBRefEntry> primaries = new ArrayList<>();
       DBRefEntry[] tmp = new DBRefEntry[1];
       for (DBRefEntry ref : dbrefs)
       {
@@ -1904,4 +1926,73 @@ public class Sequence extends ASequence implements SequenceI
 
     return count;
   }
+
+  @Override
+  public String getSequenceStringFromIterator(Iterator<int[]> it)
+  {
+    StringBuilder newSequence = new StringBuilder();
+    while (it.hasNext())
+    {
+      int[] block = it.next();
+      if (it.hasNext())
+      {
+        newSequence.append(getSequence(block[0], block[1] + 1));
+      }
+      else
+      {
+        newSequence.append(getSequence(block[0], block[1]));
+      }
+    }
+
+    return newSequence.toString();
+  }
+
+  @Override
+  public int firstResidueOutsideIterator(Iterator<int[]> regions)
+  {
+    int start = 0;
+
+    if (!regions.hasNext())
+    {
+      return findIndex(getStart()) - 1;
+    }
+
+    // Simply walk along the sequence whilst watching for region
+    // boundaries
+    int hideStart = getLength();
+    int hideEnd = -1;
+    boolean foundStart = false;
+
+    // step through the non-gapped positions of the sequence
+    for (int i = getStart(); i <= getEnd() && (!foundStart); i++)
+    {
+      // get alignment position of this residue in the sequence
+      int p = findIndex(i) - 1;
+
+      // update region start/end
+      while (hideEnd < p && regions.hasNext())
+      {
+        int[] region = regions.next();
+        hideStart = region[0];
+        hideEnd = region[1];
+      }
+      if (hideEnd < p)
+      {
+        hideStart = getLength();
+      }
+      // update boundary for sequence
+      if (p < hideStart)
+      {
+        start = p;
+        foundStart = true;
+      }
+    }
+
+    if (foundStart)
+    {
+      return start;
+    }
+    // otherwise, sequence was completely hidden
+    return 0;
+  }
 }
index 2f3e925..b22e48f 100755 (executable)
@@ -23,6 +23,7 @@ package jalview.datamodel;
 import jalview.datamodel.features.SequenceFeaturesI;
 
 import java.util.BitSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Vector;
 
@@ -223,6 +224,13 @@ public interface SequenceI extends ASequenceI
   public int[] gapMap();
 
   /**
+   * Build a bitset corresponding to sequence gaps
+   * 
+   * @return a BitSet where set values correspond to gaps in the sequence
+   */
+  public BitSet gapBitset();
+
+  /**
    * Returns an int array where indices correspond to each position in sequence
    * char array and the element value gives the result of findPosition for that
    * index in the sequence.
@@ -524,4 +532,25 @@ public interface SequenceI extends ASequenceI
    * @param c2
    */
   public int replace(char c1, char c2);
+
+  /**
+   * Returns the sequence string constructed from the substrings of a sequence
+   * defined by the int[] ranges provided by an iterator. E.g. the iterator
+   * could iterate over all visible regions of the alignment
+   * 
+   * @param it
+   *          the iterator to use
+   * @return a String corresponding to the sequence
+   */
+  public String getSequenceStringFromIterator(Iterator<int[]> it);
+
+  /**
+   * Locate the first position in this sequence which is not contained in an
+   * iterator region. If no such position exists, return 0
+   * 
+   * @param it
+   *          iterator over regions
+   * @return first residue not contained in regions
+   */
+  public int firstResidueOutsideIterator(Iterator<int[]> it);
 }
diff --git a/src/jalview/datamodel/StartRegionIterator.java b/src/jalview/datamodel/StartRegionIterator.java
new file mode 100644 (file)
index 0000000..bd036e4
--- /dev/null
@@ -0,0 +1,123 @@
+package jalview.datamodel;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An iterator which iterates over visible start positions of hidden column
+ * regions in a range.
+ */
+public class StartRegionIterator implements Iterator<Integer>
+{
+  // start position to iterate from
+  private int start;
+
+  // end position to iterate to
+  private int end;
+
+  // current index in hiddenColumns
+  private int currentPosition = 0;
+
+  // local copy or reference to hiddenColumns
+  private List<Integer> positions = null;
+
+  /**
+   * Construct an iterator over hiddenColums bounded at [lowerBound,upperBound]
+   * 
+   * @param lowerBound
+   *          lower bound to iterate from
+   * @param upperBound
+   *          upper bound to iterate to
+   * @param useCopyCols
+   *          whether to make a local copy of hiddenColumns for iteration (set
+   *          to true if calling from outwith the HiddenColumns class)
+   */
+  StartRegionIterator(int lowerBound, int upperBound,
+          List<int[]> hiddenColumns)
+  {
+    this(null, lowerBound, upperBound, hiddenColumns);
+  }
+
+  /**
+   * Construct an iterator over hiddenColums bounded at [lowerBound,upperBound]
+   * 
+   * @param pos
+   *          a hidden cursor position to start from - may be null
+   * @param lowerBound
+   *          lower bound to iterate from - will be ignored if pos != null
+   * @param upperBound
+   *          upper bound to iterate to
+   * @param hiddenColumns
+   *          the hidden columns collection to use
+   */
+  StartRegionIterator(HiddenCursorPosition pos, int lowerBound,
+          int upperBound, List<int[]> hiddenColumns)
+  {
+    start = lowerBound;
+    end = upperBound;
+
+    if (hiddenColumns != null)
+    {
+      positions = new ArrayList<>(hiddenColumns.size());
+
+      // navigate to start, keeping count of hidden columns
+      int i = 0;
+      int hiddenSoFar = 0;
+      
+      if (pos != null)
+      {
+        // use the cursor position provided
+        i = pos.getRegionIndex();
+        hiddenSoFar = pos.getHiddenSoFar();
+      }
+      else
+      {
+        // navigate to start
+        while ((i < hiddenColumns.size())
+                && (hiddenColumns.get(i)[0] < start + hiddenSoFar))
+        {
+          int[] region = hiddenColumns.get(i);
+          hiddenSoFar += region[1] - region[0] + 1;
+          i++;
+        }
+      }
+
+      // iterate from start to end, adding start positions of each
+      // hidden region. Positions are visible columns count, not absolute
+      while (i < hiddenColumns.size()
+              && (hiddenColumns.get(i)[0] <= end + hiddenSoFar))
+      {
+        int[] region = hiddenColumns.get(i);
+        positions.add(region[0] - hiddenSoFar);
+        hiddenSoFar += region[1] - region[0] + 1;
+        i++;
+      }
+    }
+    else
+    {
+      positions = new ArrayList<>();
+    }
+
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return (currentPosition < positions.size());
+  }
+
+  /**
+   * Get next hidden region start position
+   * 
+   * @return the start position in *visible* coordinates
+   */
+  @Override
+  public Integer next()
+  {
+    int result = positions.get(currentPosition);
+    currentPosition++;
+    return result;
+  }
+}
+
index e9437a7..4ca51b5 100644 (file)
@@ -32,17 +32,17 @@ public class VisibleColsCollection implements AlignmentColsCollectionI
 
   HiddenColumns hidden;
 
-  public VisibleColsCollection(int s, int e, AlignmentI al)
+  public VisibleColsCollection(int s, int e, HiddenColumns h)
   {
     start = s;
     end = e;
-    hidden = al.getHiddenColumns();
+    hidden = h;
   }
 
   @Override
   public Iterator<Integer> iterator()
   {
-    return new VisibleColsIterator(start, end, hidden);
+    return hidden.getVisibleColsIterator(start, end);
   }
 
   @Override
diff --git a/src/jalview/datamodel/VisibleColsIterator.java b/src/jalview/datamodel/VisibleColsIterator.java
deleted file mode 100644 (file)
index 9de468d..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
- * 
- * This file is part of Jalview.
- * 
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *  
- * Jalview is distributed in the hope that it will be useful, but 
- * WITHOUT ANY WARRANTY; without even the implied warranty 
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
- * PURPOSE.  See the GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.datamodel;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-/**
- * An iterator which iterates over all visible columns in an alignment
- * 
- * @author kmourao
- *
- */
-public class VisibleColsIterator implements Iterator<Integer>
-{
-  private int last;
-
-  private int current;
-
-  private int next;
-
-  private List<int[]> hidden;
-
-  private int lasthiddenregion;
-
-  public VisibleColsIterator(int firstcol, int lastcol,
-          HiddenColumns hiddenCols)
-  {
-    last = lastcol;
-    current = firstcol;
-    next = firstcol;
-    hidden = hiddenCols.getHiddenColumnsCopy();
-    lasthiddenregion = -1;
-
-    if (hidden != null)
-    {
-      int i = 0;
-      for (i = 0; i < hidden.size(); ++i)
-      {
-        if (current >= hidden.get(i)[0] && current <= hidden.get(i)[1])
-        {
-          // current is hidden, move to right
-          current = hidden.get(i)[1] + 1;
-          next = current;
-        }
-        if (current < hidden.get(i)[0])
-        {
-          break;
-        }
-      }
-      lasthiddenregion = i - 1;
-
-      for (i = hidden.size() - 1; i >= 0; --i)
-      {
-        if (last >= hidden.get(i)[0] && last <= hidden.get(i)[1])
-        {
-          // last is hidden, move to left
-          last = hidden.get(i)[0] - 1;
-        }
-        if (last > hidden.get(i)[1])
-        {
-          break;
-        }
-      }
-    }
-  }
-
-  @Override
-  public boolean hasNext()
-  {
-    return next <= last;
-  }
-
-  @Override
-  public Integer next()
-  {
-    if (next > last)
-    {
-      throw new NoSuchElementException();
-    }
-    current = next;
-    if ((hidden != null) && (lasthiddenregion + 1 < hidden.size()))
-    {
-      // still some more hidden regions
-      if (next + 1 < hidden.get(lasthiddenregion + 1)[0])
-      {
-        // next+1 is still before the next hidden region
-        next++;
-      }
-      else if ((next + 1 >= hidden.get(lasthiddenregion + 1)[0])
-              && (next + 1 <= hidden.get(lasthiddenregion + 1)[1]))
-      {
-        // next + 1 is in the next hidden region
-        next = hidden.get(lasthiddenregion + 1)[1] + 1;
-        lasthiddenregion++;
-      }
-    }
-    else
-    {
-      // finished with hidden regions, just increment normally
-      next++;
-    }
-    return current;
-  }
-
-  @Override
-  public void remove()
-  {
-    throw new UnsupportedOperationException();
-  }
-}
diff --git a/src/jalview/datamodel/VisibleContigsIterator.java b/src/jalview/datamodel/VisibleContigsIterator.java
new file mode 100644 (file)
index 0000000..6d3f43d
--- /dev/null
@@ -0,0 +1,96 @@
+package jalview.datamodel;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An iterator which iterates over visible regions in a range. Provides a
+ * special "endsAtHidden" indicator to allow callers to determine if the final
+ * visible column is adjacent to a hidden region.
+ */
+public class VisibleContigsIterator implements Iterator<int[]>
+{
+  private List<int[]> vcontigs = new ArrayList<>();
+
+  private int currentPosition = 0;
+
+  private boolean endsAtHidden = false;
+
+  VisibleContigsIterator(int start, int end,
+          List<int[]> hiddenColumns)
+  {
+    if (hiddenColumns != null && hiddenColumns.size() > 0)
+    {
+      int vstart = start;
+      int hideStart;
+      int hideEnd;
+
+      for (int[] region : hiddenColumns)
+      {
+        endsAtHidden = false;
+        hideStart = region[0];
+        hideEnd = region[1];
+
+        // navigate to start
+        if (hideEnd < vstart)
+        {
+          continue;
+        }
+        if (hideStart > vstart)
+        {
+          if (end - 1 > hideStart - 1)
+          {
+            int[] contig = new int[] { vstart, hideStart - 1 };
+            vcontigs.add(contig);
+            endsAtHidden = true;
+          }
+          else
+          {
+            int[] contig = new int[] { vstart, end - 1 };
+            vcontigs.add(contig);
+          }
+        }
+        vstart = hideEnd + 1;
+
+        // exit if we're past the end
+        if (vstart >= end)
+        {
+          break;
+        }
+      }
+
+      if (vstart < end)
+      {
+        int[] contig = new int[] { vstart, end - 1 };
+        vcontigs.add(contig);
+        endsAtHidden = false;
+      }
+    }
+    else
+    {
+      int[] contig = new int[] { start, end - 1 };
+      vcontigs.add(contig);
+    }
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return (currentPosition < vcontigs.size());
+  }
+
+  @Override
+  public int[] next()
+  {
+    int[] result = vcontigs.get(currentPosition);
+    currentPosition++;
+    return result;
+  }
+
+  public boolean endsAtHidden()
+  {
+    return endsAtHidden;
+  }
+}
+
index e10ff4a..9981559 100644 (file)
@@ -1862,23 +1862,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    ArrayList<int[]> hiddenColumns = null;
+    HiddenColumns hiddenColumns = null;
     if (viewport.hasHiddenColumns())
     {
-      hiddenColumns = new ArrayList<>();
       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
       int hiddenCutoff = viewport.getSelectionGroup().getEndRes();
-      ArrayList<int[]> hiddenRegions = viewport.getAlignment()
-              .getHiddenColumns().getHiddenColumnsCopy();
-      for (int[] region : hiddenRegions)
-      {
-        if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff)
-        {
-          hiddenColumns
-                  .add(new int[]
-                  { region[0] - hiddenOffset, region[1] - hiddenOffset });
-        }
-      }
+
+      // create new HiddenColumns object with copy of hidden regions
+      // between startRes and endRes, offset by startRes
+      hiddenColumns = new HiddenColumns(
+              viewport.getAlignment().getHiddenColumns(), hiddenOffset,
+              hiddenCutoff, hiddenOffset);
     }
 
     Desktop.jalviewClipboard = new Object[] { seqs,
@@ -2207,11 +2201,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         if (Desktop.jalviewClipboard != null
                 && Desktop.jalviewClipboard[2] != null)
         {
-          List<int[]> hc = (List<int[]>) Desktop.jalviewClipboard[2];
-          for (int[] region : hc)
-          {
-            af.viewport.hideColumns(region[0], region[1]);
-          }
+          HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
+          af.viewport.setHiddenColumns(hc);
         }
 
         // >>>This is a fix for the moment, until a better solution is
@@ -2266,11 +2257,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       if (Desktop.jalviewClipboard != null
               && Desktop.jalviewClipboard[2] != null)
       {
-        List<int[]> hc = (List<int[]>) Desktop.jalviewClipboard[2];
-        for (int region[] : hc)
-        {
-          af.viewport.hideColumns(region[0], region[1]);
-        }
+        HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
+        af.viewport.setHiddenColumns(hc);
       }
 
       // >>>This is a fix for the moment, until a better solution is
@@ -4639,11 +4627,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
             new JnetAnnotationMaker();
             JnetAnnotationMaker.add_annotation(predictions,
                     viewport.getAlignment(), 0, false);
-            SequenceI repseq = viewport.getAlignment().getSequenceAt(0);
-            viewport.getAlignment().setSeqrep(repseq);
-            HiddenColumns cs = new HiddenColumns();
-            cs.hideInsertionsFor(repseq);
-            viewport.getAlignment().setHiddenColumns(cs);
+            viewport.getAlignment().setupJPredAlignment();
             isAnnotation = true;
           }
           // else if (IdentifyFile.FeaturesFile.equals(format))
index 4d09084..7e77bec 100644 (file)
@@ -22,7 +22,6 @@ package jalview.gui;
 
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
-import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureColourI;
@@ -36,7 +35,6 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceGroup;
@@ -58,10 +56,9 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Rectangle;
-import java.util.ArrayList;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Vector;
 
 import javax.swing.JInternalFrame;
 
@@ -453,10 +450,10 @@ public class AlignViewport extends AlignmentViewport
    *          area
    * @return
    */
-  public int[] getViewAsVisibleContigs(boolean selectedRegionOnly)
+  public Iterator<int[]> getViewAsVisibleContigs(boolean selectedRegionOnly)
   {
-    int[] viscontigs = null;
-    int start = 0, end = 0;
+    int start = 0;
+    int end = 0;
     if (selectedRegionOnly && selectionGroup != null)
     {
       start = selectionGroup.getStartRes();
@@ -466,8 +463,8 @@ public class AlignViewport extends AlignmentViewport
     {
       end = alignment.getWidth();
     }
-    viscontigs = alignment.getHiddenColumns().getVisibleContigs(start, end);
-    return viscontigs;
+    return (alignment.getHiddenColumns().getVisContigsIterator(start, end,
+            false));
   }
 
   /**
@@ -604,7 +601,7 @@ public class AlignViewport extends AlignmentViewport
     return validCharWidth;
   }
 
-  private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<String, AutoCalcSetting>();
+  private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<>();
 
   public AutoCalcSetting getCalcIdSettingsFor(String calcId)
   {
index 68b1998..f5634d2 100644 (file)
@@ -419,8 +419,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (av.hasHiddenColumns())
     {
       HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-      start = hidden.findColumnPosition(start);
-      end = hidden.findColumnPosition(end);
+      start = hidden.absoluteToVisibleColumn(start);
+      end = hidden.absoluteToVisibleColumn(end);
       if (start == end)
       {
         if (!hidden.isVisible(r[0]))
@@ -678,7 +678,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         // reset the width to exclude hidden columns
         width = av.getAlignment().getHiddenColumns()
-                .findColumnPosition(width);
+                .absoluteToVisibleColumn(width);
       }
 
       hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
@@ -1176,7 +1176,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+              .absoluteToVisibleColumn(maxwidth) - 1;
     }
 
     int resWidth = getSeqPanel().seqCanvas
@@ -1368,7 +1368,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth);
+              .absoluteToVisibleColumn(maxwidth);
     }
 
     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
@@ -1586,7 +1586,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+              .absoluteToVisibleColumn(maxwidth) - 1;
     }
 
     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
index 15f1fe0..b58269d 100755 (executable)
@@ -25,6 +25,7 @@ import jalview.analysis.AlignmentUtils;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -50,9 +51,11 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.regex.Pattern;
 
 import javax.swing.JCheckBoxMenuItem;
@@ -934,8 +937,9 @@ public class AnnotationLabels extends JPanel
     Alignment ds = new Alignment(dseqs);
     if (av.hasHiddenColumns())
     {
-      omitHidden = av.getAlignment().getHiddenColumns()
-              .getVisibleSequenceStrings(0, sq.getLength(), seqs);
+      Iterator<int[]> it = av.getAlignment().getHiddenColumns()
+              .getVisContigsIterator(0, sq.getLength(), false);
+      omitHidden = new String[] { sq.getSequenceStringFromIterator(it) };
     }
 
     int[] alignmentStartEnd = new int[] { 0, ds.getWidth() - 1 };
@@ -951,12 +955,12 @@ public class AnnotationLabels extends JPanel
     Toolkit.getDefaultToolkit().getSystemClipboard()
             .setContents(new StringSelection(output), Desktop.instance);
 
-    ArrayList<int[]> hiddenColumns = null;
+    HiddenColumns hiddenColumns = null;
 
     if (av.hasHiddenColumns())
     {
-      hiddenColumns = av.getAlignment().getHiddenColumns()
-              .getHiddenColumnsCopy();
+      hiddenColumns = new HiddenColumns(
+              av.getAlignment().getHiddenColumns());
     }
 
     Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
index c39a87d..71cdc9c 100755 (executable)
@@ -724,7 +724,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     if (av.hasHiddenColumns())
     {
       column = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(column);
+              .visibleToAbsoluteColumn(column);
     }
 
     AlignmentAnnotation ann = aa[row];
index 8f13eca..cd7b0b7 100755 (executable)
@@ -380,7 +380,7 @@ public class IdCanvas extends JPanel implements ViewportListenerI
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+              .absoluteToVisibleColumn(maxwidth) - 1;
     }
 
     int annotationHeight = 0;
index 4a15024..c7ec757 100644 (file)
@@ -1389,9 +1389,10 @@ public class Jalview2XML
         }
         else
         {
-          ArrayList<int[]> hiddenRegions = hidden.getHiddenColumnsCopy();
-          for (int[] region : hiddenRegions)
+          Iterator<int[]> hiddenRegions = hidden.iterator();
+          while (hiddenRegions.hasNext())
           {
+            int[] region = hiddenRegions.next();
             HiddenColumns hc = new HiddenColumns();
             hc.setStart(region[0]);
             hc.setEnd(region[1]);
index 850a09a..759c63b 100644 (file)
@@ -1453,15 +1453,8 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
 
   protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
   {
-
-    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);
-    }
+    HiddenColumns hidden = ap.av.getAlignment().getHiddenColumns();
+    BitSet inserts = new BitSet();
 
     boolean markedPopup = false;
     // mark inserts in current selection
@@ -1469,10 +1462,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
     {
       // 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);
+              ap.av.getSelectionGroup().getEndRes() + 1); // TODO why +1?
 
       // now clear columns without gaps
       for (SequenceI sq : ap.av.getSelectionGroup().getSequences())
@@ -1483,29 +1473,18 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
         }
         inserts.and(sq.getInsertionsAsBits());
       }
-    }
-    else
-    {
-      // initially, mark all columns to be hidden
-      inserts.set(0, ap.av.getAlignment().getWidth());
-
-      // and clear out old hidden regions completely
-      mask.clear();
+      hidden.clearAndHideColumns(inserts, ap.av.getSelectionGroup().getStartRes(),
+              ap.av.getSelectionGroup().getEndRes());
     }
 
     // now mark for sequence under popup if we haven't already done it
-    if (!markedPopup && sequence != null)
+    else if (!markedPopup && sequence != null)
     {
-      inserts.and(sequence.getInsertionsAsBits());
-    }
+      inserts.or(sequence.getInsertionsAsBits());
 
-    // finally, preserve hidden regions outside selection
-    inserts.or(mask);
-
-    // and set hidden columns accordingly
-    hidden.hideMarkedBits(inserts);
-
-    ap.av.getAlignment().setHiddenColumns(hidden);
+      // and set hidden columns accordingly
+      hidden.hideColumns(inserts);
+    }
     refresh();
   }
 
index cb7f0da..e6bba02 100755 (executable)
@@ -42,6 +42,7 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.beans.PropertyChangeEvent;
+import java.util.Iterator;
 import java.util.List;
 
 import javax.swing.JMenuItem;
@@ -112,7 +113,7 @@ public class ScalePanel extends JPanel
 
     if (av.hasHiddenColumns())
     {
-      x = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(x);
+      x = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(x);
     }
 
     if (x >= av.getAlignment().getWidth())
@@ -174,7 +175,7 @@ public class ScalePanel extends JPanel
       });
       pop.add(item);
 
-      if (av.getAlignment().getHiddenColumns().hasHiddenColumns())
+      if (av.getAlignment().getHiddenColumns().hasMultiHiddenColumnRegions())
       {
         item = new JMenuItem(MessageManager.getString("action.reveal_all"));
         item.addActionListener(new ActionListener()
@@ -281,7 +282,7 @@ public class ScalePanel extends JPanel
     if (av.hasHiddenColumns())
     {
       res = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(res);
+              .visibleToAbsoluteColumn(res);
     }
 
     if (res >= av.getAlignment().getWidth())
@@ -336,7 +337,7 @@ public class ScalePanel extends JPanel
     int res = (evt.getX() / av.getCharWidth())
             + av.getRanges().getStartRes();
     res = Math.max(0, res);
-    res = hidden.adjustForHiddenColumns(res);
+    res = hidden.visibleToAbsoluteColumn(res);
     res = Math.min(res, av.getAlignment().getWidth() - 1);
     min = Math.min(res, min);
     max = Math.max(res, max);
@@ -392,7 +393,7 @@ public class ScalePanel extends JPanel
     reveal = av.getAlignment().getHiddenColumns()
             .getRegionWithEdgeAtRes(res);
 
-    res = av.getAlignment().getHiddenColumns().adjustForHiddenColumns(res);
+    res = av.getAlignment().getHiddenColumns().visibleToAbsoluteColumn(res);
 
     ToolTipManager.sharedInstance().registerComponent(this);
     this.setToolTipText(
@@ -459,7 +460,7 @@ public class ScalePanel extends JPanel
         {
           if (hidden.isVisible(sel))
           {
-            sel = hidden.findColumnPosition(sel);
+            sel = hidden.absoluteToVisibleColumn(sel);
           }
           else
           {
@@ -489,23 +490,18 @@ public class ScalePanel extends JPanel
 
       if (av.getShowHiddenMarkers())
       {
-        List<Integer> positions = hidden.findHiddenRegionPositions();
-        for (int pos : positions)
+        Iterator<Integer> it = hidden.getStartRegionIterator(startx,
+                startx + widthx + 1);
+        while (it.hasNext())
         {
-          res = pos - startx;
-
-          if (res < 0 || res > widthx)
-          {
-            continue;
-          }
+          res = it.next() - startx;
 
           gg.fillPolygon(
                   new int[]
-                  { -1 + res * avCharWidth - avCharHeight / 4,
-                      -1 + res * avCharWidth + avCharHeight / 4,
-                      -1 + res * avCharWidth },
-                  new int[]
-                  { y, y, y + 2 * yOf }, 3);
+          { -1 + res * avCharWidth - avCharHeight / 4,
+              -1 + res * avCharWidth + avCharHeight / 4,
+              -1 + res * avCharWidth }, new int[]
+          { y, y, y + 2 * yOf }, 3);
         }
       }
     }
index 7d182b2..2d8eb7d 100755 (executable)
@@ -25,6 +25,7 @@ import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.VisibleContigsIterator;
 import jalview.renderer.ScaleRenderer;
 import jalview.renderer.ScaleRenderer.ScaleMark;
 import jalview.util.Comparison;
@@ -42,6 +43,7 @@ import java.awt.RenderingHints;
 import java.awt.Shape;
 import java.awt.image.BufferedImage;
 import java.beans.PropertyChangeEvent;
+import java.util.Iterator;
 import java.util.List;
 
 import javax.swing.JComponent;
@@ -198,8 +200,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     if (av.hasHiddenColumns())
     {
       HiddenColumns hiddenColumns = av.getAlignment().getHiddenColumns();
-      startX = hiddenColumns.adjustForHiddenColumns(startx);
-      endX = hiddenColumns.adjustForHiddenColumns(endx);
+      startX = hiddenColumns.visibleToAbsoluteColumn(startx);
+      endX = hiddenColumns.visibleToAbsoluteColumn(endx);
     }
     FontMetrics fm = getFontMetrics(av.getFont());
 
@@ -896,11 +898,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     int charWidth = av.getCharWidth();
 
     g.setColor(Color.blue);
+    int res;
     HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-    List<Integer> positions = hidden.findHiddenRegionPositions();
-    for (int pos : positions)
+
+    Iterator<Integer> it = hidden.getStartRegionIterator(startColumn,
+            endColumn);
+    while (it.hasNext())
     {
-      int res = pos - startColumn;
+      res = it.next() - startColumn;
 
       if (res < 0 || res > endColumn - startColumn + 1)
       {
@@ -949,7 +954,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth);
+              .absoluteToVisibleColumn(maxwidth);
     }
 
     // chop the wrapped alignment extent up into panel-sized blocks and treat
@@ -1027,29 +1032,23 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     else
     {
       int screenY = 0;
-      final int screenYMax = endRes - startRes;
-      int blockStart = startRes;
-      int blockEnd = endRes;
+      int blockStart;
+      int blockEnd;
 
-      for (int[] region : av.getAlignment().getHiddenColumns()
-              .getHiddenColumnsCopy())
-      {
-        int hideStart = region[0];
-        int hideEnd = region[1];
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      VisibleContigsIterator regions = hidden
+              .getVisContigsIterator(startRes, endRes + 1, true);
 
-        if (hideStart <= blockStart)
-        {
-          blockStart += (hideEnd - hideStart) + 1;
-          continue;
-        }
+      while (regions.hasNext())
+      {
+        int[] region = regions.next();
+        blockEnd = region[1];
+        blockStart = region[0];
 
         /*
          * draw up to just before the next hidden region, or the end of
          * the visible region, whichever comes first
          */
-        blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
-                - screenY);
-
         g1.translate(screenY * charWidth, 0);
 
         draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
@@ -1058,7 +1057,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
          * draw the downline of the hidden column marker (ScalePanel draws the
          * triangle on top) if we reached it
          */
-        if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
+        if (av.getShowHiddenMarkers()
+                && (regions.hasNext() || regions.endsAtHidden()))
         {
           g1.setColor(Color.blue);
 
@@ -1069,23 +1069,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
         g1.translate(-screenY * charWidth, 0);
         screenY += blockEnd - blockStart + 1;
-        blockStart = hideEnd + 1;
-
-        if (screenY > screenYMax)
-        {
-          // already rendered last block
-          return;
-        }
-      }
-
-      if (screenY <= screenYMax)
-      {
-        // remaining visible region to render
-        blockEnd = blockStart + screenYMax - screenY;
-        g1.translate(screenY * charWidth, 0);
-        draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
-
-        g1.translate(-screenY * charWidth, 0);
       }
     }
 
@@ -1284,7 +1267,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
       // convert the cursorX into a position on the visible alignment
       int cursor_xpos = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(cursorX);
+              .absoluteToVisibleColumn(cursorX);
 
       if (av.getAlignment().getHiddenColumns().isVisible(cursorX))
       {
@@ -1383,22 +1366,17 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     {
       // package into blocks of visible columns
       int screenY = 0;
-      int blockStart = startRes;
-      int blockEnd = endRes;
+      int blockStart;
+      int blockEnd;
 
-      for (int[] region : av.getAlignment().getHiddenColumns()
-              .getHiddenColumnsCopy())
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      VisibleContigsIterator regions = hidden
+              .getVisContigsIterator(startRes, endRes + 1, true);
+      while (regions.hasNext())
       {
-        int hideStart = region[0];
-        int hideEnd = region[1];
-
-        if (hideStart <= blockStart)
-        {
-          blockStart += (hideEnd - hideStart) + 1;
-          continue;
-        }
-
-        blockEnd = hideStart - 1;
+        int[] region = regions.next();
+        blockEnd = region[1];
+        blockStart = region[0];
 
         g.translate(screenY * charWidth, 0);
         drawPartialGroupOutline(g, group,
@@ -1406,24 +1384,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
         g.translate(-screenY * charWidth, 0);
         screenY += blockEnd - blockStart + 1;
-        blockStart = hideEnd + 1;
-
-        if (screenY > (endRes - startRes))
-        {
-          // already rendered last block
-          break;
-        }
-      }
-
-      if (screenY <= (endRes - startRes))
-      {
-        // remaining visible region to render
-        blockEnd = blockStart + (endRes - startRes) - screenY;
-        g.translate(screenY * charWidth, 0);
-        drawPartialGroupOutline(g, group,
-                blockStart, blockEnd, startSeq, endSeq, offset);
-        
-        g.translate(-screenY * charWidth, 0);
       }
     }
   }
@@ -1687,9 +1647,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     if (av.hasHiddenColumns())
     {
       firstVisibleColumn = alignment.getHiddenColumns()
-              .adjustForHiddenColumns(firstVisibleColumn);
+              .visibleToAbsoluteColumn(firstVisibleColumn);
       lastVisibleColumn = alignment.getHiddenColumns()
-              .adjustForHiddenColumns(lastVisibleColumn);
+              .visibleToAbsoluteColumn(lastVisibleColumn);
     }
 
     for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
@@ -1732,8 +1692,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       if (av.hasHiddenColumns())
       {
         firstCol = alignment.getHiddenColumns()
-                .findColumnPosition(firstCol);
-        lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
+                .absoluteToVisibleColumn(firstCol);
+        lastCol = alignment.getHiddenColumns().absoluteToVisibleColumn(lastCol);
       }
       int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
       int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
@@ -1790,14 +1750,30 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         scrollX = -range;
       }
     }
-    // Both scrolling and resizing change viewport ranges: scrolling changes
-    // both start and end points, but resize only changes end values.
-    // Here we only want to fastpaint on a scroll, with resize using a normal
-    // paint, so scroll events are identified as changes to the horizontal or
-    // vertical start value.
-    if (eventName.equals(ViewportRanges.STARTRES))
-    {
-      if (av.getWrapAlignment())
+      // Both scrolling and resizing change viewport ranges: scrolling changes
+      // both start and end points, but resize only changes end values.
+      // Here we only want to fastpaint on a scroll, with resize using a normal
+      // paint, so scroll events are identified as changes to the horizontal or
+      // vertical start value.
+      if (eventName.equals(ViewportRanges.STARTRES))
+      {
+         if (av.getWrapAlignment())
+          {
+            fastPaintWrapped(scrollX);
+          }
+          else
+          {
+            fastPaint(scrollX, 0);
+          }
+      }
+      else if (eventName.equals(ViewportRanges.STARTSEQ))
+      {
+        // scroll
+        fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+      }
+      else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
+      {
+        if (av.getWrapAlignment())
         {
           fastPaintWrapped(scrollX);
         }
@@ -1817,14 +1793,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       {
         fastPaintWrapped(scrollX);
       }
-      else
-      {
-        fastPaint(scrollX, 0);
-      }
-      // bizarrely, we only need to scroll on the x value here as fastpaint
-      // copies the full height of the image anyway. Passing in the y value
-      // causes nasty repaint artefacts, which only disappear on a full
-      // repaint.
     }
   }
 
@@ -2119,9 +2087,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     if (av.hasHiddenColumns())
     {
       firstVisibleColumn = alignment.getHiddenColumns()
-              .adjustForHiddenColumns(firstVisibleColumn);
+              .visibleToAbsoluteColumn(firstVisibleColumn);
       lastVisibleColumn = alignment.getHiddenColumns()
-              .adjustForHiddenColumns(lastVisibleColumn);
+              .visibleToAbsoluteColumn(lastVisibleColumn);
     }
 
     int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
@@ -2160,7 +2128,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
               if (av.hasHiddenColumns())
               {
                 displayColumn = alignment.getHiddenColumns()
-                        .findColumnPosition(displayColumn);
+                        .absoluteToVisibleColumn(displayColumn);
               }
 
               /*
index 61cac46..84b85e9 100644 (file)
@@ -250,7 +250,7 @@ public class SeqPanel extends JPanel
     if (av.hasHiddenColumns())
     {
       res = av.getAlignment().getHiddenColumns()
-              .adjustForHiddenColumns(res);
+              .visibleToAbsoluteColumn(res);
     }
 
     return res;
@@ -363,13 +363,25 @@ public class SeqPanel extends JPanel
       int original = seqCanvas.cursorX - dx;
       int maxWidth = av.getAlignment().getWidth();
 
-      // TODO: once JAL-2759 is ready, change this loop to something more
-      // efficient
-      while (!hidden.isVisible(seqCanvas.cursorX)
-              && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0
-              && dx != 0)
+      if (!hidden.isVisible(seqCanvas.cursorX))
       {
-        seqCanvas.cursorX += dx;
+        int visx = hidden.absoluteToVisibleColumn(seqCanvas.cursorX - dx);
+        int[] region = hidden.getRegionWithEdgeAtRes(visx);
+
+        if (region != null) // just in case
+        {
+          if (dx == 1)
+          {
+            // moving right
+            seqCanvas.cursorX = region[1] + 1;
+          }
+          else if (dx == -1)
+          {
+            // moving left
+            seqCanvas.cursorX = region[0] - 1;
+          }
+        }
+        seqCanvas.cursorX = (seqCanvas.cursorX < 0) ? 0 : seqCanvas.cursorX;
       }
 
       if (seqCanvas.cursorX >= maxWidth
@@ -424,7 +436,7 @@ public class SeqPanel extends JPanel
       {
         // scrollToWrappedVisible expects x-value to have hidden cols subtracted
         int x = av.getAlignment().getHiddenColumns()
-                .findColumnPosition(seqCanvas.cursorX);
+                .absoluteToVisibleColumn(seqCanvas.cursorX);
         av.getRanges().scrollToWrappedVisible(x);
       }
       else
@@ -1259,9 +1271,9 @@ public class SeqPanel extends JPanel
     {
       fixedColumns = true;
       int y1 = av.getAlignment().getHiddenColumns()
-              .getHiddenBoundaryLeft(startres);
+              .getNextHiddenBoundary(true, startres);
       int y2 = av.getAlignment().getHiddenColumns()
-              .getHiddenBoundaryRight(startres);
+              .getNextHiddenBoundary(false, startres);
 
       if ((insertGap && startres > y1 && lastres < y1)
               || (!insertGap && startres < y2 && lastres > y2))
@@ -1337,7 +1349,8 @@ public class SeqPanel extends JPanel
           if (sg.getSize() == av.getAlignment().getHeight())
           {
             if ((av.hasHiddenColumns() && startres < av.getAlignment()
-                    .getHiddenColumns().getHiddenBoundaryRight(startres)))
+                    .getHiddenColumns()
+                    .getNextHiddenBoundary(false, startres)))
             {
               endEditing();
               return;
index d2086e0..973cfe8 100644 (file)
@@ -1074,16 +1074,16 @@ public class VamsasApplication implements SelectionSource, VamsasSource
                   }
                   else
                   {
-                    // int[] intervals = colsel.getVisibleContigs(
-                    // seqsel.getStartRes(), seqsel.getEndRes() + 1);
-                    int[] intervals = hidden.getVisibleContigs(
-                            seqsel.getStartRes(), seqsel.getEndRes() + 1);
-                    for (int iv = 0; iv < intervals.length; iv += 2)
+                    Iterator<int[]> intervals = hidden
+                            .getVisContigsIterator(seqsel.getStartRes(),
+                                    seqsel.getEndRes() + 1, false);
+                    while (intervals.hasNext())
                     {
+                      int[] region = intervals.next();
                       Seg s = new Seg();
-                      s.setStart(intervals[iv] + 1); // vamsas indices begin at
-                      // 1, not zero.
-                      s.setEnd(intervals[iv + 1] + 1);
+                      s.setStart(region[0] + 1); // vamsas indices begin at 1,
+                                                 // not zero.
+                      s.setEnd(region[1] + 1);
                       s.setInclusive(true);
                       range.addSeg(s);
                     }
index 00476d6..e578a45 100755 (executable)
@@ -968,7 +968,7 @@ public class AnnotationFile
             else
             {
               // consider deferring this till after the file has been parsed ?
-              hidden.hideInsertionsFor(sr);
+              hidden.hideList(sr.getInsertions());
             }
           }
           modified = true;
index 7647a16..6d3c18a 100755 (executable)
@@ -212,12 +212,12 @@ public class FormatAdapter extends AppletFormatAdapter
           AlignmentAnnotation na = new AlignmentAnnotation(ala[i]);
           if (selgp != null)
           {
-            hidden.makeVisibleAnnotation(selgp.getStartRes(),
-                    selgp.getEndRes(), na);
+            na.makeVisibleAnnotation(selgp.getStartRes(), selgp.getEndRes(),
+                    hidden);
           }
           else
           {
-            hidden.makeVisibleAnnotation(na);
+            na.makeVisibleAnnotation(hidden);
           }
           alv.addAnnotation(na);
         }
index 8a80d41..adca17e 100644 (file)
@@ -163,7 +163,7 @@ public class AnnotationRenderer
   {
     g.setColor(STEM_COLOUR);
     int sCol = (lastSSX / charWidth)
-            + hiddenColumns.adjustForHiddenColumns(startRes);
+            + hiddenColumns.visibleToAbsoluteColumn(startRes);
     int x1 = lastSSX;
     int x2 = (x * charWidth);
 
@@ -230,7 +230,7 @@ public class AnnotationRenderer
 
     g.setColor(nonCanColor);
     int sCol = (lastSSX / charWidth)
-            + hiddenColumns.adjustForHiddenColumns(startRes);
+            + hiddenColumns.visibleToAbsoluteColumn(startRes);
     int x1 = lastSSX;
     int x2 = (x * charWidth);
 
@@ -602,7 +602,7 @@ public class AnnotationRenderer
         {
           if (hasHiddenColumns)
           {
-            column = hiddenColumns.adjustForHiddenColumns(startRes + x);
+            column = hiddenColumns.visibleToAbsoluteColumn(startRes + x);
             if (column > row_annotations.length - 1)
             {
               break;
@@ -1150,7 +1150,7 @@ public class AnnotationRenderer
     g.setColor(HELIX_COLOUR);
 
     int sCol = (lastSSX / charWidth)
-            + hiddenColumns.adjustForHiddenColumns(startRes);
+            + hiddenColumns.visibleToAbsoluteColumn(startRes);
     int x1 = lastSSX;
     int x2 = (x * charWidth);
 
@@ -1250,7 +1250,7 @@ public class AnnotationRenderer
       column = sRes + x;
       if (hasHiddenColumns)
       {
-        column = hiddenColumns.adjustForHiddenColumns(column);
+        column = hiddenColumns.visibleToAbsoluteColumn(column);
       }
 
       if (column > aaMax)
@@ -1330,7 +1330,7 @@ public class AnnotationRenderer
       column = sRes + x;
       if (hasHiddenColumns)
       {
-        column = hiddenColumns.adjustForHiddenColumns(column);
+        column = hiddenColumns.visibleToAbsoluteColumn(column);
       }
 
       if (column > aaMax)
index d92608c..dc3272f 100644 (file)
 package jalview.renderer;
 
 import jalview.api.AlignViewportI;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -60,27 +62,19 @@ public class ScaleRenderer
       column = col;
       text = txt;
     }
-
-    /**
-     * String representation for inspection when debugging only
-     */
-    @Override
-    public String toString()
-    {
-      return String.format("%s:%d:%s", major ? "major" : "minor", column,
-              text);
-    }
   }
 
   /**
-   * Calculates position markers on the alignment ruler
+   * calculate positions markers on the alignment ruler
    * 
    * @param av
    * @param startx
-   *          left-most column in visible view (0..)
+   *          left-most column in visible view
    * @param endx
-   *          - right-most column in visible view (0..)
-   * @return
+   *          - right-most column in visible view
+   * @return List of ScaleMark holding boolean: true/false for major/minor mark,
+   *         marker position in alignment column coords, a String to be rendered
+   *         at the position (or null)
    */
   public List<ScaleMark> calculateMarks(AlignViewportI av, int startx,
           int endx)
@@ -88,17 +82,27 @@ public class ScaleRenderer
     int scalestartx = (startx / 10) * 10;
 
     SequenceI refSeq = av.getAlignment().getSeqrep();
-    int refSp = 0, refStartI = 0, refEndI = -1;
+    int refSp = 0;
+    int refStartI = 0;
+    int refEndI = -1;
+
+    HiddenColumns hc = av.getAlignment().getHiddenColumns();
+
     if (refSeq != null)
     {
-      // find bounds and set origin appopriately
-      // locate first visible position for this sequence
-      int[] refbounds = av.getAlignment().getHiddenColumns()
-              .locateVisibleBoundsOfSequence(refSeq);
+      // find bounds and set origin appropriately
+      // locate first residue in sequence which is not hidden
+      Iterator<int[]> it = hc.iterator();
+      int index = refSeq.firstResidueOutsideIterator(it);
+      refSp = hc.absoluteToVisibleColumn(index);
+
+      refStartI = refSeq.findIndex(refSeq.getStart()) - 1;
+
+      int seqlength = refSeq.getLength();
+      // get sequence position past the end of the sequence
+      int pastEndPos = refSeq.findPosition(seqlength + 1);
+      refEndI = refSeq.findIndex(pastEndPos - 1) - 1;
 
-      refSp = refbounds[0];
-      refStartI = refbounds[4];
-      refEndI = refbounds[5];
       scalestartx = refSp + ((scalestartx - refSp) / 10) * 10;
     }
 
@@ -106,41 +110,40 @@ public class ScaleRenderer
     {
       scalestartx += 5;
     }
-    List<ScaleMark> marks = new ArrayList<ScaleMark>();
+    List<ScaleMark> marks = new ArrayList<>();
+    String string;
+    int refN, iadj;
     // todo: add a 'reference origin column' to set column number relative to
     for (int i = scalestartx; i <= endx; i += 5)
     {
       if (((i - refSp) % 10) == 0)
       {
-        String text;
         if (refSeq == null)
         {
-          int iadj = av.getAlignment().getHiddenColumns()
-                  .adjustForHiddenColumns(i - 1) + 1;
-          text = String.valueOf(iadj);
+          iadj = hc.visibleToAbsoluteColumn(i - 1) + 1;
+          string = String.valueOf(iadj);
         }
         else
         {
-          int iadj = av.getAlignment().getHiddenColumns()
-                  .adjustForHiddenColumns(i - 1);
-          int refN = refSeq.findPosition(iadj);
+          iadj = hc.visibleToAbsoluteColumn(i - 1);
+          refN = refSeq.findPosition(iadj);
           // TODO show bounds if position is a gap
           // - ie L--R -> "1L|2R" for
           // marker
           if (iadj < refStartI)
           {
-            text = String.valueOf(iadj - refStartI);
+            string = String.valueOf(iadj - refStartI);
           }
           else if (iadj > refEndI)
           {
-            text = "+" + String.valueOf(iadj - refEndI);
+            string = "+" + String.valueOf(iadj - refEndI);
           }
           else
           {
-            text = String.valueOf(refN) + refSeq.getCharAt(iadj);
+            string = String.valueOf(refN) + refSeq.getCharAt(iadj);
           }
         }
-        marks.add(new ScaleMark(true, i - startx - 1, text));
+        marks.add(new ScaleMark(true, i - startx - 1, string));
       }
       else
       {
index 9c5c109..5a26ed6 100644 (file)
@@ -542,9 +542,11 @@ public final class MappingUtils
               toSequences, fromGapChar);
     }
 
-    for (int[] hidden : hiddencols.getHiddenColumnsCopy())
+    Iterator<int[]> regions = hiddencols.iterator();
+    while (regions.hasNext())
     {
-      mapHiddenColumns(hidden, codonFrames, newHidden, fromSequences,
+      mapHiddenColumns(regions.next(), codonFrames, newHidden,
+              fromSequences,
               toSequences, fromGapChar);
     }
     return; // mappedColumns;
index bca3c85..1366ada 100644 (file)
@@ -67,6 +67,7 @@ import java.util.BitSet;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -1740,8 +1741,12 @@ public abstract class AlignmentViewport
     if (alignment.getHiddenColumns() != null
             && alignment.getHiddenColumns().hasHiddenColumns())
     {
-      selection = alignment.getHiddenColumns()
-              .getVisibleSequenceStrings(start, end, seqs);
+      for (i = 0; i < iSize; i++)
+      {
+        Iterator<int[]> blocks = alignment.getHiddenColumns()
+                .getVisContigsIterator(start, end + 1, false);
+        selection[i] = seqs[i].getSequenceStringFromIterator(blocks);
+      }
     }
     else
     {
@@ -1768,10 +1773,10 @@ public abstract class AlignmentViewport
       {
         if (start == 0)
         {
-          start = hidden.adjustForHiddenColumns(start);
+          start = hidden.visibleToAbsoluteColumn(start);
         }
 
-        end = hidden.getHiddenBoundaryRight(start);
+        end = hidden.getNextHiddenBoundary(false, start);
         if (start == end)
         {
           end = max;
@@ -1786,8 +1791,8 @@ public abstract class AlignmentViewport
 
       if (hidden != null && hidden.hasHiddenColumns())
       {
-        start = hidden.adjustForHiddenColumns(end);
-        start = hidden.getHiddenBoundaryLeft(start) + 1;
+        start = hidden.visibleToAbsoluteColumn(end);
+        start = hidden.getNextHiddenBoundary(true, start) + 1;
       }
     } while (end < max);
 
@@ -1809,13 +1814,13 @@ public abstract class AlignmentViewport
         AlignmentAnnotation clone = new AlignmentAnnotation(annot);
         if (selectedOnly && selectionGroup != null)
         {
-          alignment.getHiddenColumns().makeVisibleAnnotation(
+          clone.makeVisibleAnnotation(
                   selectionGroup.getStartRes(), selectionGroup.getEndRes(),
-                  clone);
+                  alignment.getHiddenColumns());
         }
         else
         {
-          alignment.getHiddenColumns().makeVisibleAnnotation(clone);
+          clone.makeVisibleAnnotation(alignment.getHiddenColumns());
         }
         ala.add(clone);
       }
index f58e711..de90a21 100644 (file)
@@ -154,7 +154,7 @@ public class OverviewDimensionsHideHidden extends OverviewDimensions
   public AlignmentColsCollectionI getColumns(AlignmentI al)
   {
     return new VisibleColsCollection(0,
-            ranges.getAbsoluteAlignmentWidth() - 1, al);
+            ranges.getAbsoluteAlignmentWidth() - 1, al.getHiddenColumns());
   }
 
   @Override
index eaa3413..3aa6e1b 100644 (file)
@@ -80,7 +80,7 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
     int yAsSeq = getTopYFromCentreY(mousey, hiddenSeqs);
 
     // convert to visible positions
-    int visXAsRes = hiddenCols.findColumnPosition(xAsRes);
+    int visXAsRes = hiddenCols.absoluteToVisibleColumn(xAsRes);
     yAsSeq = hiddenSeqs.adjustForHiddenSeqs(
             hiddenSeqs.findIndexWithoutHiddenSeqs(yAsSeq));
     yAsSeq = Math.max(yAsSeq, 0); // -1 if before first visible sequence
@@ -101,8 +101,8 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
     // get mouse location in viewport coords, add translation in viewport
     // coords,
     // convert back to pixel coords
-    int vpx = Math.round(mousex * widthRatio);
-    int visXAsRes = hiddenCols.findColumnPosition(vpx) + xdiff;
+    int vpx = Math.round((float) mousex * alwidth / width);
+    int visXAsRes = hiddenCols.absoluteToVisibleColumn(vpx) + xdiff;
 
     int vpy = Math.round(mousey * heightRatio);
     int visYAsRes = hiddenSeqs.findIndexWithoutHiddenSeqs(vpy) + ydiff;
@@ -143,7 +143,7 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
     int vpwidth = ranges.getViewportWidth();
 
     // check in case we went off the edge of the alignment
-    int visAlignWidth = hiddenCols.findColumnPosition(alwidth - 1);
+    int visAlignWidth = hiddenCols.absoluteToVisibleColumn(alwidth - 1);
     if (visXAsRes + vpwidth - 1 > visAlignWidth)
     {
       // went past the end of the alignment, adjust backwards
@@ -151,8 +151,8 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
       // if last position was before the end of the alignment, need to update
       if (ranges.getEndRes() < visAlignWidth)
       {
-        visXAsRes = hiddenCols.findColumnPosition(hiddenCols
-                .subtractVisibleColumns(vpwidth - 1, alwidth - 1));
+        visXAsRes = hiddenCols.absoluteToVisibleColumn(hiddenCols
+                .offsetByVisibleColumns(-(vpwidth - 1), alwidth - 1));
       }
       else
       {
@@ -202,8 +202,8 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
           HiddenColumns hiddenCols)
   {
     // work with absolute values of startRes and endRes
-    int startRes = hiddenCols.adjustForHiddenColumns(ranges.getStartRes());
-    int endRes = hiddenCols.adjustForHiddenColumns(ranges.getEndRes());
+    int startRes = hiddenCols.visibleToAbsoluteColumn(ranges.getStartRes());
+    int endRes = hiddenCols.visibleToAbsoluteColumn(ranges.getEndRes());
 
     // work with absolute values of startSeq and endSeq
     int startSeq = hiddenSeqs.adjustForHiddenSeqs(ranges.getStartSeq());
@@ -245,8 +245,8 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
   @Override
   protected int getLeftXFromCentreX(int mousex, HiddenColumns hidden)
   {
-    int vpx = Math.round(mousex * widthRatio);
-    return hidden.subtractVisibleColumns(ranges.getViewportWidth() / 2,
+    int vpx = Math.round((float) mousex * alwidth / width);
+    return hidden.offsetByVisibleColumns(-ranges.getViewportWidth() / 2,
             vpx);
   }
 
@@ -272,7 +272,7 @@ public class OverviewDimensionsShowHidden extends OverviewDimensions
     int vpx = Math.round(x * widthRatio);
     int vpy = Math.round(y * heightRatio);
 
-    xdiff = ranges.getStartRes() - hiddenCols.findColumnPosition(vpx);
+    xdiff = ranges.getStartRes() - hiddenCols.absoluteToVisibleColumn(vpx);
     ydiff = ranges.getStartSeq()
             - hiddenSeqs.findIndexWithoutHiddenSeqs(vpy);
   }
index c7a3fa1..691e492 100644 (file)
@@ -609,14 +609,14 @@ public class ViewportRanges extends ViewportProperties
     }
     
     HiddenColumns hidden = al.getHiddenColumns();
-    while (x < hidden.adjustForHiddenColumns(startRes))
+    while (x < hidden.visibleToAbsoluteColumn(startRes))
     {
       if (!scrollRight(false))
       {
         break;
       }
     }
-    while (x > hidden.adjustForHiddenColumns(endRes))
+    while (x > hidden.visibleToAbsoluteColumn(endRes))
     {
       if (!scrollRight(true))
       {
@@ -638,7 +638,7 @@ public class ViewportRanges extends ViewportProperties
     boolean changedLocation = false;
 
     // convert the x,y location to visible coordinates
-    int visX = al.getHiddenColumns().findColumnPosition(x);
+    int visX = al.getHiddenColumns().absoluteToVisibleColumn(x);
     int visY = al.getHiddenSequences().findIndexWithoutHiddenSeqs(y);
 
     // if (vis_x,vis_y) is already visible don't do anything
index a239625..23d9eb0 100644 (file)
@@ -235,8 +235,7 @@ class JPredThread extends JWS1Thread implements WSClientI
           {
             // Adjust input view for gaps
             // propagate insertions into profile
-            alhidden = HiddenColumns.propagateInsertions(profileseq, al,
-                    input);
+            alhidden = al.propagateInsertions(profileseq, input);
           }
         }
       }
index d2fa99a..6a31b31 100644 (file)
@@ -38,6 +38,7 @@ import jalview.io.FileFormat;
 import jalview.io.FormatAdapter;
 
 import java.io.IOException;
+import java.util.Iterator;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -135,7 +136,9 @@ public class DnaTest
             FileFormat.Fasta);
     HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(alf, cs);
-    Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, alf.getWidth(),
+            false);
+    Dna dna = new Dna(av, contigs);
     AlignmentI translated = dna.translateCdna();
     assertNotNull("Couldn't do a full width translation of test data.",
             translated);
@@ -163,7 +166,8 @@ public class DnaTest
         cs.hideColumns(0, ipos - 1);
       }
       cs.hideColumns(ipos + vwidth, alf.getWidth());
-      int[] vcontigs = cs.getVisibleContigs(0, alf.getWidth());
+      Iterator<int[]> vcontigs = cs.getVisContigsIterator(0,
+              alf.getWidth(), false);
       AlignViewportI av = new AlignViewport(alf, cs);
       Dna dna = new Dna(av, vcontigs);
       AlignmentI transAlf = dna.translateCdna();
@@ -190,7 +194,9 @@ public class DnaTest
             DataSourceType.PASTE, FileFormat.Fasta);
     HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(alf, cs);
-    Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, alf.getWidth(),
+            false);
+    Dna dna = new Dna(av, contigs);
     AlignmentI translated = dna.translateCdna();
     String aa = translated.getSequenceAt(0).getSequenceAsString();
     assertEquals(
@@ -213,7 +219,9 @@ public class DnaTest
     cs.hideColumns(24, 35); // hide codons 9-12
     cs.hideColumns(177, 191); // hide codons 60-64
     AlignViewportI av = new AlignViewport(alf, cs);
-    Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, alf.getWidth(),
+            false);
+    Dna dna = new Dna(av, contigs);
     AlignmentI translated = dna.translateCdna();
     String aa = translated.getSequenceAt(0).getSequenceAsString();
     assertEquals("AACDDGGGGHHIIIKKLLLLLLMNNPPPPQQRRRRRRSSSSSSTTTTVVVVW", aa);
@@ -298,7 +306,9 @@ public class DnaTest
             .generate(12, 8, 97, 5, 5);
     HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(cdna, cs);
-    Dna dna = new Dna(av, new int[] { 0, cdna.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, cdna.getWidth(),
+            false);
+    Dna dna = new Dna(av, contigs);
     AlignmentI translated = dna.translateCdna();
 
     /*
@@ -313,7 +323,8 @@ public class DnaTest
     }
     AlignmentI cdnaReordered = new Alignment(sorted);
     av = new AlignViewport(cdnaReordered, cs);
-    dna = new Dna(av, new int[] { 0, cdna.getWidth() - 1 });
+    contigs = cs.getVisContigsIterator(0, cdna.getWidth(), false);
+    dna = new Dna(av, contigs);
     AlignmentI translated2 = dna.translateCdna();
 
     /*
@@ -544,7 +555,9 @@ public class DnaTest
 
     HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(al, cs);
-    Dna testee = new Dna(av, new int[] { 0, al.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, al.getWidth(),
+            false);
+    Dna testee = new Dna(av, contigs);
     AlignmentI reversed = testee.reverseCdna(false);
     assertEquals(1, reversed.getHeight());
     assertEquals(seqRev, reversed.getSequenceAt(0).getSequenceAsString());
index e47e9d6..19a725e 100644 (file)
@@ -20,8 +20,8 @@
  */
 package jalview.datamodel;
 
+import static org.testng.Assert.assertNull;
 import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertNull;
 
 import jalview.analysis.AlignSeq;
 import jalview.gui.JvOptionPane;
@@ -335,4 +335,92 @@ public class AlignmentAnnotationTests
     Assert.assertTrue(ann.isQuantitative(),
             "Mixed 'E' annotation set should be quantitative.");
   }
+
+  @Test(groups = "Functional")
+  public void testMakeVisibleAnnotation()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Annotation[] anns = new Annotation[] { null, null, new Annotation(1),
+        new Annotation(2), new Annotation(3), null, null, new Annotation(4),
+        new Annotation(5), new Annotation(6), new Annotation(7),
+        new Annotation(8) };
+    AlignmentAnnotation ann = new AlignmentAnnotation("an", "some an",
+            anns);
+
+    // null annotations
+    AlignmentAnnotation emptyann = new AlignmentAnnotation("an", "some ann",
+            null);
+    emptyann.makeVisibleAnnotation(h);
+    assertNull(emptyann.annotations);
+
+    emptyann.makeVisibleAnnotation(3, 4, h);
+    assertNull(emptyann.annotations);
+
+    // without bounds, does everything
+    ann.makeVisibleAnnotation(h);
+    assertEquals(12, ann.annotations.length);
+    assertNull(ann.annotations[0]);
+    assertNull(ann.annotations[1]);
+    assertEquals(1.0f, ann.annotations[2].value);
+    assertEquals(2.0f, ann.annotations[3].value);
+    assertEquals(3.0f, ann.annotations[4].value);
+    assertNull(ann.annotations[5]);
+    assertNull(ann.annotations[6]);
+    assertEquals(4.0f, ann.annotations[7].value);
+    assertEquals(5.0f, ann.annotations[8].value);
+    assertEquals(6.0f, ann.annotations[9].value);
+    assertEquals(7.0f, ann.annotations[10].value);
+    assertEquals(8.0f, ann.annotations[11].value);
+
+    // without hidden cols, just truncates
+    ann.makeVisibleAnnotation(3, 5, h);
+    assertEquals(3, ann.annotations.length);
+    assertEquals(2.0f, ann.annotations[0].value);
+    assertEquals(3.0f, ann.annotations[1].value);
+    assertNull(ann.annotations[2]);
+
+    anns = new Annotation[] { null, null, new Annotation(1),
+        new Annotation(2), new Annotation(3), null, null, new Annotation(4),
+        new Annotation(5), new Annotation(6), new Annotation(7),
+        new Annotation(8) };
+    ann = new AlignmentAnnotation("an", "some an", anns);
+    h.hideColumns(4, 7);
+    ann.makeVisibleAnnotation(1, 9, h);
+    assertEquals(5, ann.annotations.length);
+    assertNull(ann.annotations[0]);
+    assertEquals(1.0f, ann.annotations[1].value);
+    assertEquals(2.0f, ann.annotations[2].value);
+    assertEquals(5.0f, ann.annotations[3].value);
+    assertEquals(6.0f, ann.annotations[4].value);
+
+    anns = new Annotation[] { null, null, new Annotation(1),
+        new Annotation(2), new Annotation(3), null, null, new Annotation(4),
+        new Annotation(5), new Annotation(6), new Annotation(7),
+        new Annotation(8) };
+    ann = new AlignmentAnnotation("an", "some an", anns);
+    h.hideColumns(1, 2);
+    ann.makeVisibleAnnotation(1, 9, h);
+    assertEquals(3, ann.annotations.length);
+    assertEquals(2.0f, ann.annotations[0].value);
+    assertEquals(5.0f, ann.annotations[1].value);
+    assertEquals(6.0f, ann.annotations[2].value);
+
+    anns = new Annotation[] { null, null, new Annotation(1),
+        new Annotation(2), new Annotation(3), null, null, new Annotation(4),
+        new Annotation(5), new Annotation(6), new Annotation(7),
+        new Annotation(8), new Annotation(9), new Annotation(10),
+        new Annotation(11), new Annotation(12), new Annotation(13),
+        new Annotation(14), new Annotation(15) };
+    ann = new AlignmentAnnotation("an", "some an", anns);
+    h = new HiddenColumns();
+    h.hideColumns(5, 18);
+    h.hideColumns(20, 21);
+    ann.makeVisibleAnnotation(1, 21, h);
+    assertEquals(5, ann.annotations.length);
+    assertEquals(1.0f, ann.annotations[1].value);
+    assertEquals(2.0f, ann.annotations[2].value);
+    assertEquals(3.0f, ann.annotations[3].value);
+    assertNull(ann.annotations[0]);
+    assertNull(ann.annotations[4]);
+  }
 }
index 74ac146..266b90a 100644 (file)
@@ -34,6 +34,7 @@ import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
 import jalview.io.FileFormatI;
 import jalview.io.FormatAdapter;
+import jalview.util.Comparison;
 import jalview.util.MapList;
 
 import java.io.IOException;
@@ -1350,4 +1351,135 @@ public class AlignmentTest
     assertEquals(seq, ala.sequenceRef);
     assertEquals("", ala.calcId);
   }
+
+  @Test(groups = "Functional")
+  public void testPropagateInsertions()
+  {
+    // create an alignment with no gaps - this will be the profile seq and other
+    // JPRED seqs
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(25, 10, 1234, 0, 0);
+
+    // get the profileseq
+    SequenceI profileseq = al.getSequenceAt(0);
+    SequenceI gappedseq = new Sequence(profileseq);
+    gappedseq.insertCharAt(5, al.getGapCharacter());
+    gappedseq.insertCharAt(6, al.getGapCharacter());
+    gappedseq.insertCharAt(7, al.getGapCharacter());
+    gappedseq.insertCharAt(8, al.getGapCharacter());
+
+    // force different kinds of padding
+    al.getSequenceAt(3).deleteChars(2, 23);
+    al.getSequenceAt(4).deleteChars(2, 27);
+    al.getSequenceAt(5).deleteChars(10, 27);
+
+    // create an alignment view with the gapped sequence
+    SequenceI[] seqs = new SequenceI[1];
+    seqs[0] = gappedseq;
+    AlignmentI newal = new Alignment(seqs);
+    HiddenColumns hidden = new HiddenColumns();
+    hidden.hideColumns(15, 17);
+
+    AlignmentView view = new AlignmentView(newal, hidden, null, true, false,
+            false);
+
+    // confirm that original contigs are as expected
+    Iterator<int[]> visible = hidden.getVisContigsIterator(0, 25, false);
+    int[] region = visible.next();
+    assertEquals("[0, 14]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[18, 24]", Arrays.toString(region));
+
+    // propagate insertions
+    HiddenColumns result = al.propagateInsertions(profileseq, view);
+
+    // confirm that the contigs have changed to account for the gaps
+    visible = result.getVisContigsIterator(0, 25, false);
+    region = visible.next();
+    assertEquals("[0, 10]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[14, 24]", Arrays.toString(region));
+
+    // confirm the alignment has been changed so that the other sequences have
+    // gaps inserted where the columns are hidden
+    assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[10]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[11]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[12]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[13]));
+    assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[14]));
+
+  }
+
+  @Test(groups = "Functional")
+  public void testPropagateInsertionsOverlap()
+  {
+    // test propagateInsertions where gaps and hiddenColumns overlap
+
+    // create an alignment with no gaps - this will be the profile seq and other
+    // JPRED seqs
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(20, 10, 1234, 0, 0);
+
+    // get the profileseq
+    SequenceI profileseq = al.getSequenceAt(0);
+    SequenceI gappedseq = new Sequence(profileseq);
+    gappedseq.insertCharAt(5, al.getGapCharacter());
+    gappedseq.insertCharAt(6, al.getGapCharacter());
+    gappedseq.insertCharAt(7, al.getGapCharacter());
+    gappedseq.insertCharAt(8, al.getGapCharacter());
+
+    // create an alignment view with the gapped sequence
+    SequenceI[] seqs = new SequenceI[1];
+    seqs[0] = gappedseq;
+    AlignmentI newal = new Alignment(seqs);
+
+    // hide columns so that some overlap with the gaps
+    HiddenColumns hidden = new HiddenColumns();
+    hidden.hideColumns(7, 10);
+
+    AlignmentView view = new AlignmentView(newal, hidden, null, true, false,
+            false);
+
+    // confirm that original contigs are as expected
+    Iterator<int[]> visible = hidden.getVisContigsIterator(0, 20, false);
+    int[] region = visible.next();
+    assertEquals("[0, 6]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[11, 19]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // propagate insertions
+    HiddenColumns result = al.propagateInsertions(profileseq, view);
+
+    // confirm that the contigs have changed to account for the gaps
+    visible = result.getVisContigsIterator(0, 20, false);
+    region = visible.next();
+    assertEquals("[0, 4]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[7, 19]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // confirm the alignment has been changed so that the other sequences have
+    // gaps inserted where the columns are hidden
+    assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[4]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[5]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[6]));
+    assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[7]));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testPadGaps()
+  {
+    SequenceI seq1 = new Sequence("seq1", "ABCDEF--");
+    SequenceI seq2 = new Sequence("seq2", "-JKLMNO--");
+    SequenceI seq3 = new Sequence("seq2", "-PQR");
+    AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2, seq3 });
+    a.setGapCharacter('.'); // this replaces existing gaps
+    assertEquals("ABCDEF..", seq1.getSequenceAsString());
+    a.padGaps();
+    // trailing gaps are pruned, short sequences padded with gap character
+    assertEquals("ABCDEF.", seq1.getSequenceAsString());
+    assertEquals(".JKLMNO", seq2.getSequenceAsString());
+    assertEquals(".PQR...", seq3.getSequenceAsString());
+  }
 }
index e99e952..8709961 100644 (file)
@@ -32,6 +32,7 @@ import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.ConcurrentModificationException;
+import java.util.Iterator;
 import java.util.List;
 
 import org.testng.annotations.BeforeClass;
@@ -132,9 +133,9 @@ public class ColumnSelectionTest
     // hide column 5 (and adjacent):
     cs.hideSelectedColumns(5, al.getHiddenColumns());
     // 4,5,6 now hidden:
-    List<int[]> hidden = al.getHiddenColumns().getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
+    Iterator<int[]> regions = al.getHiddenColumns().iterator();
+    assertEquals(1, al.getHiddenColumns().getNumberOfRegions());
+    assertEquals("[4, 6]", Arrays.toString(regions.next()));
     // none now selected:
     assertTrue(cs.getSelected().isEmpty());
 
@@ -145,9 +146,9 @@ public class ColumnSelectionTest
     cs.addElement(5);
     cs.addElement(6);
     cs.hideSelectedColumns(4, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
+    regions = al.getHiddenColumns().iterator();
+    assertEquals(1, al.getHiddenColumns().getNumberOfRegions());
+    assertEquals("[4, 6]", Arrays.toString(regions.next()));
     assertTrue(cs.getSelected().isEmpty());
 
     // repeat, hiding column (4, 5 and) 6
@@ -157,9 +158,9 @@ public class ColumnSelectionTest
     cs.addElement(5);
     cs.addElement(6);
     cs.hideSelectedColumns(6, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
+    regions = al.getHiddenColumns().iterator();
+    assertEquals(1, al.getHiddenColumns().getNumberOfRegions());
+    assertEquals("[4, 6]", Arrays.toString(regions.next()));
     assertTrue(cs.getSelected().isEmpty());
 
     // repeat, with _only_ adjacent columns selected
@@ -168,9 +169,9 @@ public class ColumnSelectionTest
     cs.addElement(4);
     cs.addElement(6);
     cs.hideSelectedColumns(5, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
+    regions = al.getHiddenColumns().iterator();
+    assertEquals(1, al.getHiddenColumns().getNumberOfRegions());
+    assertEquals("[4, 6]", Arrays.toString(regions.next()));
     assertTrue(cs.getSelected().isEmpty());
   }
 
@@ -196,12 +197,12 @@ public class ColumnSelectionTest
 
     cs.hideSelectedColumns(al);
     assertTrue(cs.getSelected().isEmpty());
-    List<int[]> hidden = cols.getHiddenColumnsCopy();
-    assertEquals(4, hidden.size());
-    assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
-    assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
-    assertEquals("[15, 18]", Arrays.toString(hidden.get(2)));
-    assertEquals("[20, 22]", Arrays.toString(hidden.get(3)));
+    Iterator<int[]> regions = cols.iterator();
+    assertEquals(4, cols.getNumberOfRegions());
+    assertEquals("[2, 4]", Arrays.toString(regions.next()));
+    assertEquals("[7, 9]", Arrays.toString(regions.next()));
+    assertEquals("[15, 18]", Arrays.toString(regions.next()));
+    assertEquals("[20, 22]", Arrays.toString(regions.next()));
   }
 
   /**
diff --git a/test/jalview/datamodel/HiddenColumnsCursorTest.java b/test/jalview/datamodel/HiddenColumnsCursorTest.java
new file mode 100644 (file)
index 0000000..97402b8
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.datamodel;
+
+import static org.testng.Assert.assertNull;
+import static org.testng.AssertJUnit.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+public class HiddenColumnsCursorTest
+{
+
+  @Test(groups = { "Functional" })
+  public void testConstructor()
+  {
+    HiddenColumnsCursor cursor = new HiddenColumnsCursor();
+    assertNull(cursor.findRegionForColumn(0, false));
+
+    List<int[]> hlist = new ArrayList<>();
+    cursor = new HiddenColumnsCursor(hlist);
+    assertNull(cursor.findRegionForColumn(0, false));
+
+    cursor = new HiddenColumnsCursor(hlist, 3, 12);
+    assertNull(cursor.findRegionForColumn(0, false));
+
+    hlist.add(new int[] { 3, 7 });
+    hlist.add(new int[] { 15, 25 });
+    cursor = new HiddenColumnsCursor(hlist);
+    HiddenCursorPosition p = cursor.findRegionForColumn(8, false);
+    assertEquals(1, p.getRegionIndex());
+
+    cursor = new HiddenColumnsCursor(hlist, 1, 5);
+    p = cursor.findRegionForColumn(8, false);
+    assertEquals(1, p.getRegionIndex());
+  }
+
+  /**
+   * Test the method which finds the corresponding region given a column
+   */
+  @Test(groups = { "Functional" })
+  public void testFindRegionForColumn()
+  {
+    HiddenColumnsCursor cursor = new HiddenColumnsCursor();
+    
+    HiddenCursorPosition pos = cursor.findRegionForColumn(20, false);
+    assertNull(pos);
+    
+    List<int[]> hidden = new ArrayList<>();
+    hidden.add(new int[] { 53, 76 });
+    hidden.add(new int[] { 104, 125 });
+
+    cursor = new HiddenColumnsCursor(hidden);
+
+    int regionIndex = cursor.findRegionForColumn(126, false).getRegionIndex();
+    assertEquals(2, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(125, false).getRegionIndex();
+    assertEquals(1, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(108, false).getRegionIndex();
+    assertEquals(1, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(104, false).getRegionIndex();
+    assertEquals(1, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(103, false).getRegionIndex();
+    assertEquals(1, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(77, false).getRegionIndex();
+    assertEquals(1, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(76, false).getRegionIndex();
+    assertEquals(0, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(53, false).getRegionIndex();
+    assertEquals(0, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(52, false).getRegionIndex();
+    assertEquals(0, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(0, false).getRegionIndex();
+    assertEquals(0, regionIndex);
+
+    hidden.add(new int[] { 138, 155 });
+
+    cursor = new HiddenColumnsCursor(hidden);
+
+    regionIndex = cursor.findRegionForColumn(160, false).getRegionIndex();
+    assertEquals(3, regionIndex);
+
+    regionIndex = cursor.findRegionForColumn(100, false).getRegionIndex();
+    assertEquals(1, regionIndex);
+  }
+
+  /**
+   * Test the method which counts the number of hidden columns before a column
+   */
+  @Test(groups = { "Functional" })
+  public void testFindRegionForColumn_Visible()
+  {
+    HiddenColumnsCursor cursor = new HiddenColumnsCursor();
+
+    HiddenCursorPosition pos = cursor.findRegionForColumn(20, true);
+    assertNull(pos);
+
+    List<int[]> hidden = new ArrayList<>();
+    hidden.add(new int[] { 53, 76 });
+    hidden.add(new int[] { 104, 125 });
+
+    cursor = new HiddenColumnsCursor(hidden);
+
+    int offset = cursor.findRegionForColumn(80, true).getHiddenSoFar();
+    assertEquals(46, offset);
+
+    offset = cursor.findRegionForColumn(79, true).getHiddenSoFar();
+    assertEquals(24, offset);
+
+    offset = cursor.findRegionForColumn(53, true).getHiddenSoFar();
+    assertEquals(24, offset);
+
+    offset = cursor.findRegionForColumn(52, true).getHiddenSoFar();
+    assertEquals(0, offset);
+
+    offset = cursor.findRegionForColumn(10, true).getHiddenSoFar();
+    assertEquals(0, offset);
+
+    offset = cursor.findRegionForColumn(0, true).getHiddenSoFar();
+    assertEquals(0, offset);
+
+    offset = cursor.findRegionForColumn(79, true).getHiddenSoFar();
+    assertEquals(24, offset);
+
+    offset = cursor.findRegionForColumn(80, true).getHiddenSoFar();
+    assertEquals(46, offset);
+  }
+}
index 7c88d71..2916199 100644 (file)
@@ -26,26 +26,15 @@ import static org.testng.AssertJUnit.assertFalse;
 import static org.testng.AssertJUnit.assertTrue;
 
 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 java.util.Iterator;
 
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 public class HiddenColumnsTest
 {
-
-  @BeforeClass(alwaysRun = true)
-  public void setUpJvOptionPane()
-  {
-    JvOptionPane.setInteractiveMode(false);
-    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
-  }
-
   /**
    * Test the method which counts the number of hidden columns
    */
@@ -77,22 +66,22 @@ public class HiddenColumnsTest
   public void testFindColumnPosition()
   {
     HiddenColumns cs = new HiddenColumns();
-    assertEquals(5, cs.findColumnPosition(5));
+    assertEquals(5, cs.absoluteToVisibleColumn(5));
 
     // hiding column 6 makes no difference
     cs.hideColumns(6, 6);
-    assertEquals(5, cs.findColumnPosition(5));
+    assertEquals(5, cs.absoluteToVisibleColumn(5));
 
     // hiding column 4 moves column 5 to column 4
     cs.hideColumns(4, 4);
-    assertEquals(4, cs.findColumnPosition(5));
+    assertEquals(4, cs.absoluteToVisibleColumn(5));
 
     // hiding column 4 moves column 4 to position 3
-    assertEquals(3, cs.findColumnPosition(4));
+    assertEquals(3, cs.absoluteToVisibleColumn(4));
 
     // hiding columns 1 and 2 moves column 5 to column 2
     cs.hideColumns(1, 2);
-    assertEquals(2, cs.findColumnPosition(5));
+    assertEquals(2, cs.absoluteToVisibleColumn(5));
 
     // check with > 1 hidden column regions
     // where some columns are in the hidden regions
@@ -102,105 +91,82 @@ public class HiddenColumnsTest
     cs2.hideColumns(40, 44);
 
     // hiding columns 5-10 and 20-27 moves column 8 to column 4
-    assertEquals(4, cs2.findColumnPosition(8));
+    assertEquals(4, cs2.absoluteToVisibleColumn(8));
 
     // and moves column 24 to 13
-    assertEquals(13, cs2.findColumnPosition(24));
+    assertEquals(13, cs2.absoluteToVisibleColumn(24));
 
     // and moves column 28 to 14
-    assertEquals(14, cs2.findColumnPosition(28));
+    assertEquals(14, cs2.absoluteToVisibleColumn(28));
 
     // and moves column 40 to 25
-    assertEquals(25, cs2.findColumnPosition(40));
+    assertEquals(25, cs2.absoluteToVisibleColumn(40));
 
     // check when hidden columns start at 0 that the visible column
     // is returned as 0
     HiddenColumns cs3 = new HiddenColumns();
     cs3.hideColumns(0, 4);
-    assertEquals(0, cs3.findColumnPosition(2));
+    assertEquals(0, cs3.absoluteToVisibleColumn(2));
 
+    // check that column after the last hidden region doesn't crash
+    assertEquals(46, cs2.absoluteToVisibleColumn(65));
   }
 
-  /**
-   * Test the method that finds the visible column position a given distance
-   * before another column
-   */
   @Test(groups = { "Functional" })
-  public void testFindColumnNToLeft()
+  public void testVisibleContigsIterator()
   {
     HiddenColumns cs = new HiddenColumns();
 
-    // test that without hidden columns, findColumnNToLeft returns
-    // position n to left of provided position
-    int pos = cs.subtractVisibleColumns(3, 10);
-    assertEquals(7, pos);
-
-    // 0 returns same position
-    pos = cs.subtractVisibleColumns(0, 10);
-    assertEquals(10, pos);
-
-    // overflow to left returns negative number
-    pos = cs.subtractVisibleColumns(3, 0);
-    assertEquals(-3, pos);
-
-    // test that with hidden columns to left of result column
-    // behaviour is the same as above
-    cs.hideColumns(1, 3);
-
-    // position n to left of provided position
-    pos = cs.subtractVisibleColumns(3, 10);
-    assertEquals(7, pos);
-
-    // 0 returns same position
-    pos = cs.subtractVisibleColumns(0, 10);
-    assertEquals(10, pos);
-
-    // test with one set of hidden columns between start and required position
-    cs.hideColumns(12, 15);
-    pos = cs.subtractVisibleColumns(8, 17);
-    assertEquals(5, pos);
-
-    // test with two sets of hidden columns between start and required position
-    cs.hideColumns(20, 21);
-    pos = cs.subtractVisibleColumns(8, 23);
-    assertEquals(9, pos);
-
-    // repeat last 2 tests with no hidden columns to left of required position
-    ColumnSelection colsel = new ColumnSelection();
-    cs.revealAllHiddenColumns(colsel);
-
-    // test with one set of hidden columns between start and required position
-    cs.hideColumns(12, 15);
-    pos = cs.subtractVisibleColumns(8, 17);
-    assertEquals(5, pos);
-
-    // test with two sets of hidden columns between start and required position
-    cs.hideColumns(20, 21);
-    pos = cs.subtractVisibleColumns(8, 23);
-    assertEquals(9, pos);
-
-  }
+    Iterator<int[]> visible = cs.getVisContigsIterator(3, 10, false);
+    int[] region = visible.next();
+    assertEquals("[3, 9]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
 
-  @Test(groups = { "Functional" })
-  public void testGetVisibleContigs()
-  {
-    HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(3, 6);
     cs.hideColumns(8, 9);
     cs.hideColumns(12, 12);
 
-    // start position is inclusive, end position exclusive:
-    int[] visible = cs.getVisibleContigs(1, 13);
-    assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(4, 14);
-    assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(3, 10);
-    assertEquals("[7, 7]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(4, 6);
-    assertEquals("[]", Arrays.toString(visible));
+    // Test both ends visible region
+
+    // start position is inclusive, end position exclusive
+    visible = cs.getVisContigsIterator(1, 13, false);
+    region = visible.next();
+    assertEquals("[1, 2]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[7, 7]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[10, 11]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // Test start hidden, end visible
+    visible = cs.getVisContigsIterator(4, 14, false);
+    region = visible.next();
+    assertEquals("[7, 7]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[10, 11]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[13, 13]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // Test start hidden, end hidden
+    visible = cs.getVisContigsIterator(3, 10, false);
+    region = visible.next();
+    assertEquals("[7, 7]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // Test start visible, end hidden
+    visible = cs.getVisContigsIterator(0, 13, false);
+    region = visible.next();
+    assertEquals("[0, 2]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[7, 7]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[10, 11]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // Test empty result
+    visible = cs.getVisContigsIterator(4, 6, false);
+    assertFalse(visible.hasNext());
   }
 
   @Test(groups = { "Functional" })
@@ -216,14 +182,31 @@ public class HiddenColumnsTest
     assertFalse(cs.equals(cs2));
     assertFalse(cs2.equals(cs));
 
+    // with the wrong kind of object
+    assertFalse(cs.equals(new HiddenColumnsCursor()));
+
+    // with a different hiddenColumns object - by size
+    HiddenColumns cs3 = new HiddenColumns();
+    cs3.hideColumns(2, 3);
+    assertFalse(cs.equals(cs3));
+
     // with hidden columns added in a different order
     cs2.hideColumns(6, 9);
+    assertFalse(cs.equals(cs2));
+    assertFalse(cs2.equals(cs));
+
     cs2.hideColumns(5, 8);
 
     assertTrue(cs.equals(cs2));
     assertTrue(cs.equals(cs));
     assertTrue(cs2.equals(cs));
     assertTrue(cs2.equals(cs2));
+
+    // different ranges, same size
+    cs.hideColumns(10, 12);
+    cs2.hideColumns(10, 15);
+    assertFalse(cs.equals(cs2));
+
   }
 
   @Test(groups = "Functional")
@@ -232,102 +215,50 @@ public class HiddenColumnsTest
     HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(10, 11);
     cs.hideColumns(5, 7);
+    Iterator<int[]> regions = cs.iterator();
     assertEquals("[5, 7]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
 
     HiddenColumns cs2 = new HiddenColumns(cs);
+    regions = cs2.iterator();
     assertTrue(cs2.hasHiddenColumns());
-    assertEquals(2, cs2.getHiddenColumnsCopy().size());
+    assertEquals(2, cs2.getNumberOfRegions());
     // hidden columns are held in column order
     assertEquals("[5, 7]",
-            Arrays.toString(cs2.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
     assertEquals("[10, 11]",
-            Arrays.toString(cs2.getHiddenColumnsCopy().get(1)));
+            Arrays.toString(regions.next()));
   }
 
-  /**
-   * Test the code used to locate the reference sequence ruler origin
-   */
-  @Test(groups = { "Functional" })
-  public void testLocateVisibleBoundsofSequence()
+  @Test(groups = "Functional")
+  public void testCopyConstructor2()
   {
-    // create random alignment
-    AlignmentGenerator gen = new AlignmentGenerator(false);
-    AlignmentI al = gen.generate(50, 20, 123, 5, 5);
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideColumns(10, 11);
+    cs.hideColumns(5, 7);
 
-    HiddenColumns cs = al.getHiddenColumns();
-    ColumnSelection colsel = new ColumnSelection();
+    HiddenColumns cs2 = new HiddenColumns(cs, 3, 9, 1);
+    assertTrue(cs2.hasHiddenColumns());
+    Iterator<int[]> regions = cs2.iterator();
 
-    SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
-    assertEquals(2, seq.findIndex(seq.getStart()));
-
-    // no hidden columns
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
-
-    // hidden column on gap after end of sequence - should not affect bounds
-    colsel.hideSelectedColumns(13, al.getHiddenColumns());
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+    // only [5,7] returned, offset by 1
+    assertEquals("[4, 6]",
+            Arrays.toString(regions.next()));
+    assertEquals(3, cs2.getSize());
 
-    cs.revealAllHiddenColumns(colsel);
-    // hidden column on gap before beginning of sequence - should vis bounds by
-    // one
-    colsel.hideSelectedColumns(0, al.getHiddenColumns());
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2,
-                seq.findIndex(seq.getEnd()) - 2, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
-
-    cs.revealAllHiddenColumns(colsel);
-    // hide columns around most of sequence - leave one residue remaining
-    cs.hideColumns(1, 3);
-    cs.hideColumns(6, 11);
-    assertEquals("-D",
-            cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]);
-    assertEquals(
-            Arrays.toString(new int[] { 1, 1, 3, 3,
-                seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
-    cs.revealAllHiddenColumns(colsel);
+    cs2 = new HiddenColumns(cs, 8, 15, 4);
+    regions = cs2.iterator();
+    assertTrue(cs2.hasHiddenColumns());
 
-    // hide whole sequence - should just get location of hidden region
-    // containing sequence
-    cs.hideColumns(1, 11);
-    assertEquals(
-            Arrays.toString(new int[] { 0, 1, 0, 0,
-                seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+    // only [10,11] returned, offset by 4
+    assertEquals("[6, 7]",
+            Arrays.toString(regions.next()));
+    assertEquals(2, cs2.getSize());
 
+    cs2 = new HiddenColumns(cs, 6, 10, 4);
+    assertFalse(cs2.hasHiddenColumns());
   }
 
-  @Test(groups = { "Functional" })
-  public void testLocateVisibleBoundsPathologicals()
-  {
-    // test some pathological cases we missed
-    AlignmentI al = new Alignment(new SequenceI[] { new Sequence(
-            "refseqGaptest", "KTDVTI----------NFI-----G----L") });
-    HiddenColumns cs = new HiddenColumns();
-    cs.hideInsertionsFor(al.getSequenceAt(0));
-    assertEquals(
-            "G",
-            ""
-                    + al.getSequenceAt(0).getCharAt(
-                            cs.adjustForHiddenColumns(9)));
-
-  }
 
   @Test(groups = { "Functional" })
   public void testHideColumns()
@@ -339,80 +270,103 @@ public class HiddenColumnsTest
     ColumnSelection colsel = new ColumnSelection();
     HiddenColumns cs = al.getHiddenColumns();
     colsel.hideSelectedColumns(5, al.getHiddenColumns());
-    List<int[]> hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[5, 5]", Arrays.toString(hidden.get(0)));
+    Iterator<int[]> regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[5, 5]", Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 1);
 
     colsel.hideSelectedColumns(3, al.getHiddenColumns());
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(2, hidden.size());
+    regions = cs.iterator();
+    assertEquals(2, cs.getNumberOfRegions());
     // two hidden ranges, in order:
-    assertEquals(hidden.size(), cs.getHiddenColumnsCopy().size());
-    assertEquals("[3, 3]", Arrays.toString(hidden.get(0)));
-    assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
+    assertEquals("[3, 3]", Arrays.toString(regions.next()));
+    assertEquals("[5, 5]", Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 2);
 
     // hiding column 4 expands [3, 3] to [3, 4]
     // and merges to [5, 5] to make [3, 5]
     colsel.hideSelectedColumns(4, al.getHiddenColumns());
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[3, 5]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[3, 5]", Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 3);
 
     // clear hidden columns (note they are added to selected)
     cs.revealAllHiddenColumns(colsel);
     // it is now actually null but getter returns an empty list
-    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
+    assertEquals(0, cs.getNumberOfRegions());
+    assertEquals(cs.getSize(), 0);
 
     cs.hideColumns(3, 6);
-    hidden = cs.getHiddenColumnsCopy();
-    int[] firstHiddenRange = hidden.get(0);
+    regions = cs.iterator();
+    int[] firstHiddenRange = regions.next();
     assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
+    assertEquals(cs.getSize(), 4);
 
     // adding a subrange of already hidden should do nothing
     cs.hideColumns(4, 5);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
     assertEquals("[3, 6]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 4);
     cs.hideColumns(3, 5);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
     assertEquals("[3, 6]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 4);
     cs.hideColumns(4, 6);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
     assertEquals("[3, 6]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 4);
     cs.hideColumns(3, 6);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
     assertEquals("[3, 6]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 4);
 
     cs.revealAllHiddenColumns(colsel);
     cs.hideColumns(2, 4);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[2, 4]", Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 3);
 
     // extend contiguous with 2 positions overlap
     cs.hideColumns(3, 5);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[2, 5]", Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 4);
 
     // extend contiguous with 1 position overlap
     cs.hideColumns(5, 6);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[2, 6]", Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 5);
 
     // extend contiguous with overlap both ends:
     cs.hideColumns(1, 7);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[1, 7]", Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 7);
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(15, 18);
+    cs.hideColumns(2, 4);
+    cs.hideColumns(7, 9);
+    regions = cs.iterator();
+    assertEquals(3, cs.getNumberOfRegions());
+    assertEquals("[2, 4]", Arrays.toString(regions.next()));
+    assertEquals("[7, 9]", Arrays.toString(regions.next()));
+    assertEquals("[15, 18]", Arrays.toString(regions.next()));
+    assertEquals(cs.getSize(), 10);
   }
 
   /**
@@ -424,11 +378,18 @@ public class HiddenColumnsTest
   {
     ColumnSelection colsel = new ColumnSelection();
     HiddenColumns cs = new HiddenColumns();
+
+    // test with null hidden columns
+    cs.revealHiddenColumns(5, colsel);
+    assertTrue(colsel.getSelected().isEmpty());
+
     cs.hideColumns(5, 8);
     colsel.addElement(10);
     cs.revealHiddenColumns(5, colsel);
-    // hidden columns list now null but getter returns empty list:
-    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
+
+    // hiddenColumns now empty
+    assertEquals(0, cs.getSize());
+
     // revealed columns are marked as selected (added to selection):
     assertEquals("[10, 5, 6, 7, 8]", colsel.getSelected().toString());
 
@@ -436,36 +397,67 @@ public class HiddenColumnsTest
     colsel = new ColumnSelection();
     cs = new HiddenColumns();
     cs.hideColumns(5, 8);
-    List<int[]> hidden = cs.getHiddenColumnsCopy();
+
+    int prevSize = cs.getSize();
     cs.revealHiddenColumns(6, colsel);
-    assertEquals(hidden.size(), cs.getHiddenColumnsCopy().size());
+    assertEquals(prevSize, cs.getSize());
+    assertTrue(colsel.getSelected().isEmpty());
+
+    // reveal hidden columns when there is more than one region
+    cs.hideColumns(20, 23);
+    // now there are 2 hidden regions
+    assertEquals(2, cs.getNumberOfRegions());
+
+    cs.revealHiddenColumns(20, colsel);
+
+    // hiddenColumns now has one region
+    assertEquals(1, cs.getNumberOfRegions());
+
+    // revealed columns are marked as selected (added to selection):
+    assertEquals("[20, 21, 22, 23]", colsel.getSelected().toString());
+
+    // call with a column past the end of the hidden column ranges
+    colsel.clear();
+    cs.revealHiddenColumns(20, colsel);
+    // hiddenColumns still has 1 region
+    assertEquals(1, cs.getNumberOfRegions());
     assertTrue(colsel.getSelected().isEmpty());
   }
 
   @Test(groups = { "Functional" })
   public void testRevealAllHiddenColumns()
   {
-    HiddenColumns cs = new HiddenColumns();
+    HiddenColumns hidden = new HiddenColumns();
     ColumnSelection colsel = new ColumnSelection();
-    cs.hideColumns(5, 8);
-    cs.hideColumns(2, 3);
+
+    // test with null hidden columns
+    hidden.revealAllHiddenColumns(colsel);
+    assertTrue(colsel.getSelected().isEmpty());
+
+    hidden.hideColumns(5, 8);
+    hidden.hideColumns(2, 3);
     colsel.addElement(11);
     colsel.addElement(1);
-    cs.revealAllHiddenColumns(colsel);
+    hidden.revealAllHiddenColumns(colsel);
 
     /*
      * revealing hidden columns adds them (in order) to the (unordered)
      * selection list
      */
-    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
-    assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", colsel.getSelected()
-            .toString());
+
+    // hiddenColumns now empty
+    assertEquals(0, hidden.getSize());
+
+    assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]",
+            colsel.getSelected().toString());
   }
 
   @Test(groups = { "Functional" })
   public void testIsVisible()
   {
     HiddenColumns cs = new HiddenColumns();
+    assertTrue(cs.isVisible(5));
+
     cs.hideColumns(2, 4);
     cs.hideColumns(6, 7);
     assertTrue(cs.isVisible(0));
@@ -477,6 +469,7 @@ public class HiddenColumnsTest
     assertTrue(cs.isVisible(5));
     assertFalse(cs.isVisible(6));
     assertFalse(cs.isVisible(7));
+    assertTrue(cs.isVisible(8));
   }
 
   /**
@@ -493,15 +486,17 @@ public class HiddenColumnsTest
     HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(49, 59);
     cs.hideColumns(69, 79);
-    List<int[]> hidden = cs.getHiddenColumnsCopy();
-    assertEquals(2, hidden.size());
-    assertEquals("[49, 59]", Arrays.toString(hidden.get(0)));
-    assertEquals("[69, 79]", Arrays.toString(hidden.get(1)));
+    Iterator<int[]> regions = cs.iterator();
+    assertEquals(2, cs.getNumberOfRegions());
+    assertEquals("[49, 59]", Arrays.toString(regions.next()));
+    assertEquals("[69, 79]", Arrays.toString(regions.next()));
+    assertEquals(22, cs.getSize());
 
     cs.hideColumns(48, 80);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[48, 80]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[48, 80]", Arrays.toString(regions.next()));
+    assertEquals(33, cs.getSize());
 
     /*
      * another...joining hidden ranges
@@ -512,9 +507,10 @@ public class HiddenColumnsTest
     cs.hideColumns(50, 60);
     // hiding 21-49 should merge to one range
     cs.hideColumns(21, 49);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[10, 60]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[10, 60]", Arrays.toString(regions.next()));
+    assertEquals(51, cs.getSize());
 
     /*
      * another...left overlap, subsumption, right overlap,
@@ -528,14 +524,15 @@ public class HiddenColumnsTest
     cs.hideColumns(60, 70);
 
     cs.hideColumns(15, 45);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(2, hidden.size());
-    assertEquals("[10, 50]", Arrays.toString(hidden.get(0)));
-    assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
+    regions = cs.iterator();
+    assertEquals(2, cs.getNumberOfRegions());
+    assertEquals("[10, 50]", Arrays.toString(regions.next()));
+    assertEquals("[60, 70]", Arrays.toString(regions.next()));
+    assertEquals(52, cs.getSize());
   }
 
   @Test(groups = { "Functional" })
-  public void testHideBitset()
+  public void testHideColumns_BitSet()
   {
     HiddenColumns cs;
 
@@ -544,80 +541,45 @@ public class HiddenColumnsTest
     // one hidden range
     one.set(1);
     cs = new HiddenColumns();
-    cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenColumnsCopy().size());
+    cs.hideColumns(one);
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals(1, cs.getSize());
 
     one.set(2);
     cs = new HiddenColumns();
-    cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenColumnsCopy().size());
+    cs.hideColumns(one);
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals(2, cs.getSize());
 
     one.set(3);
     cs = new HiddenColumns();
-    cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenColumnsCopy().size());
+    cs.hideColumns(one);
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals(3, cs.getSize());
 
     // split
     one.clear(2);
     cs = new HiddenColumns();
-    cs.hideMarkedBits(one);
-    assertEquals(2, cs.getHiddenColumnsCopy().size());
+    cs.hideColumns(one);
+    assertEquals(2, cs.getNumberOfRegions());
+    assertEquals(2, cs.getSize());
 
-    assertEquals(0, cs.adjustForHiddenColumns(0));
-    assertEquals(2, cs.adjustForHiddenColumns(1));
-    assertEquals(4, cs.adjustForHiddenColumns(2));
+    assertEquals(0, cs.visibleToAbsoluteColumn(0));
+    assertEquals(2, cs.visibleToAbsoluteColumn(1));
+    assertEquals(4, cs.visibleToAbsoluteColumn(2));
 
     // one again
     one.clear(1);
     cs = new HiddenColumns();
-    cs.hideMarkedBits(one);
+    cs.hideColumns(one);
+    assertEquals(1, cs.getSize());
 
-    assertEquals(1, cs.getHiddenColumnsCopy().size());
+    assertEquals(1, cs.getNumberOfRegions());
 
-    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);
-    }
-  }
-
-  @Test(groups = { "Functional" })
-  public void testFindHiddenRegionPositions()
-  {
-    HiddenColumns hc = new HiddenColumns();
-
-    List<Integer> positions = hc.findHiddenRegionPositions();
-    assertTrue(positions.isEmpty());
-
-    hc.hideColumns(3, 7);
-    hc.hideColumns(10, 10);
-    hc.hideColumns(14, 15);
-
-    positions = hc.findHiddenRegionPositions();
-    assertEquals(3, positions.size());
-    assertEquals(3, positions.get(0).intValue());
-    assertEquals(5, positions.get(1).intValue());
-    assertEquals(8, positions.get(2).intValue());
+    assertEquals(0, cs.visibleToAbsoluteColumn(0));
+    assertEquals(1, cs.visibleToAbsoluteColumn(1));
+    assertEquals(2, cs.visibleToAbsoluteColumn(2));
+    assertEquals(4, cs.visibleToAbsoluteColumn(3));
   }
 
   @Test(groups = { "Functional" })
@@ -637,7 +599,7 @@ public class HiddenColumnsTest
   }
 
   @Test(groups = "Functional")
-  public void getVisibleStartAndEndIndexTest()
+  public void testGetVisibleStartAndEndIndex()
   {
     Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
     AlignmentI align = new Alignment(new SequenceI[] { seq });
@@ -663,6 +625,13 @@ public class HiddenColumnsTest
     System.out.println(startEnd[0] + " : " + startEnd[1]);
     assertEquals(1, startEnd[0]);
     assertEquals(23, startEnd[1]);
+
+    // force lowest range to start of alignment
+    hc = new HiddenColumns();
+    hc.hideColumns(3, 4);
+    startEnd = hc.getVisibleStartAndEndIndex(align.getWidth());
+    assertEquals(0, startEnd[0]);
+    assertEquals(25, startEnd[1]);
   }
 
   @Test(groups = "Functional")
@@ -677,58 +646,683 @@ public class HiddenColumnsTest
     hc.hideColumns(10, 10);
     hc.hideColumns(14, 15);
 
-    result = hc.getRegionWithEdgeAtRes(3);
+    result = hc.getRegionWithEdgeAtRes(2);
     assertEquals(3, result[0]);
     assertEquals(7, result[1]);
 
+    result = hc.getRegionWithEdgeAtRes(4);
+    assertEquals(10, result[0]);
+    assertEquals(10, result[1]);
+
     result = hc.getRegionWithEdgeAtRes(5);
     assertEquals(10, result[0]);
     assertEquals(10, result[1]);
 
     result = hc.getRegionWithEdgeAtRes(6);
     assertNull(result);
+
+    result = hc.getRegionWithEdgeAtRes(0);
+    assertNull(result);
+
+    result = hc.getRegionWithEdgeAtRes(7);
+    assertEquals(14, result[0]);
+    assertEquals(15, result[1]);
+
+    result = hc.getRegionWithEdgeAtRes(8);
+    assertEquals(14, result[0]);
+    assertEquals(15, result[1]);
+
+    result = hc.getRegionWithEdgeAtRes(16);
+    assertNull(result);
   }
 
   @Test(groups = "Functional")
-  public void testPropagateInsertions()
+  public void testHasHiddenColumns()
   {
-    // create an alignment with no gaps - this will be the profile seq and other
-    // JPRED seqs
-    AlignmentGenerator gen = new AlignmentGenerator(false);
-    AlignmentI al = gen.generate(20, 10, 1234, 0, 0);
-
-    // get the profileseq
-    SequenceI profileseq = al.getSequenceAt(0);
-    SequenceI gappedseq = new Sequence(profileseq);
-    gappedseq.insertCharAt(5, al.getGapCharacter());
-    gappedseq.insertCharAt(6, al.getGapCharacter());
-    gappedseq.insertCharAt(7, al.getGapCharacter());
-    gappedseq.insertCharAt(8, al.getGapCharacter());
-    
-    // create an alignment view with the gapped sequence
-    SequenceI[] seqs = new SequenceI[1];
-    seqs[0] = gappedseq;
-    AlignmentI newal = new Alignment(seqs);
-    HiddenColumns hidden = new HiddenColumns();
-    hidden.hideColumns(15, 17);
-
-    AlignmentView view = new AlignmentView(newal, hidden, null, true, false,
-            false);
-
-    // confirm that original contigs are as expected
-    int[] oldcontigs = hidden.getVisibleContigs(0, 20);
-    int[] testcontigs = { 0, 14, 18, 19 };
-    assertTrue(Arrays.equals(oldcontigs, testcontigs));
-            
-    // propagate insertions
-    HiddenColumns result = HiddenColumns.propagateInsertions(profileseq, al,
-            view);
-
-    // confirm that the contigs have changed to account for the gaps
-    int[] newcontigs = result.getVisibleContigs(0, 20);
-    testcontigs[1] = 10;
-    testcontigs[2] = 14;
-    assertTrue(Arrays.equals(newcontigs, testcontigs));
-    
+    HiddenColumns h = new HiddenColumns();
+
+    // new HiddenColumns2 has no hidden cols
+    assertFalse(h.hasHiddenColumns());
+
+    // some columns hidden, returns true
+    h.hideColumns(5, 10);
+    assertTrue(h.hasHiddenColumns());
+
+    // reveal columns, no hidden cols again
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    assertFalse(h.hasHiddenColumns());
+  }
+
+  @Test(groups = "Functional")
+  public void testHasManyHiddenColumns()
+  {
+    HiddenColumns h = new HiddenColumns();
+
+    // h has no hidden cols
+    assertFalse(h.hasMultiHiddenColumnRegions());
+
+    // one set of columns hidden, returns false
+    h.hideColumns(5, 10);
+    assertFalse(h.hasMultiHiddenColumnRegions());
+
+    // two sets hidden, returns true
+    h.hideColumns(15, 17);
+    assertTrue(h.hasMultiHiddenColumnRegions());
+
+    // back to one block, asserts false
+    h.hideColumns(11, 14);
+    assertFalse(h.hasMultiHiddenColumnRegions());
+  }
+
+  @Test(groups = "Functional")
+  public void testAdjustForHiddenColumns()
+  {
+    HiddenColumns h = new HiddenColumns();
+    // returns input value when there are no hidden columns
+    assertEquals(10, h.visibleToAbsoluteColumn(10));
+
+    h.hideColumns(20, 30);
+    assertEquals(10, h.visibleToAbsoluteColumn(10));
+    assertEquals(20 + 11, h.visibleToAbsoluteColumn(20));
+    assertEquals(35 + 11, h.visibleToAbsoluteColumn(35));
+
+    h.hideColumns(5, 7);
+    assertEquals(10 + 3, h.visibleToAbsoluteColumn(10));
+    assertEquals(20 + 14, h.visibleToAbsoluteColumn(20));
+    assertEquals(35 + 14, h.visibleToAbsoluteColumn(35));
+
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    h.hideColumns(0, 1);
+    assertEquals(4, h.visibleToAbsoluteColumn(2));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetNextHiddenBoundary_Left()
+  {
+    HiddenColumns h = new HiddenColumns();
+
+    // returns same value if no hidden cols
+    assertEquals(3, h.getNextHiddenBoundary(true, 3));
+
+    h.hideColumns(5, 10);
+    assertEquals(10, h.getNextHiddenBoundary(true, 15));
+    assertEquals(3, h.getNextHiddenBoundary(true, 3));
+    assertEquals(7, h.getNextHiddenBoundary(true, 7));
+
+    h.hideColumns(15, 20);
+    assertEquals(10, h.getNextHiddenBoundary(true, 15));
+    assertEquals(20, h.getNextHiddenBoundary(true, 21));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetNextHiddenBoundary_Right()
+  {
+    HiddenColumns h = new HiddenColumns();
+
+    // returns same value if no hidden cols
+    assertEquals(3, h.getNextHiddenBoundary(false, 3));
+
+    h.hideColumns(5, 10);
+    assertEquals(5, h.getNextHiddenBoundary(false, 3));
+    assertEquals(15, h.getNextHiddenBoundary(false, 15));
+    assertEquals(7, h.getNextHiddenBoundary(false, 7));
+
+    h.hideColumns(15, 20);
+    assertEquals(15, h.getNextHiddenBoundary(false, 7));
+    assertEquals(15, h.getNextHiddenBoundary(false, 14));
+
+    // returns same value if there is no next hidden column
+    assertEquals(22, h.getNextHiddenBoundary(false, 22));
+  }
+
+  @Test(groups = "Functional")
+  public void testIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<int[]> result = h.iterator();
+    assertFalse(result.hasNext());
+
+    h.hideColumns(5, 10);
+    result = h.iterator();
+    int[] next = result.next();
+    assertEquals(5, next[0]);
+    assertEquals(10, next[1]);
+    assertFalse(result.hasNext());
+
+    h.hideColumns(22, 23);
+    result = h.iterator();
+    next = result.next();
+    assertEquals(5, next[0]);
+    assertEquals(10, next[1]);
+    next = result.next();
+    assertEquals(22, next[0]);
+    assertEquals(23, next[1]);
+    assertFalse(result.hasNext());
+
+    // test for only one hidden region at start of alignment
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    h.hideColumns(0, 1);
+    result = h.iterator();
+    next = result.next();
+    assertEquals(0, next[0]);
+    assertEquals(1, next[1]);
+    assertFalse(result.hasNext());
+  }
+
+  /* @Test(groups = "Functional")
+  public void testGetVisibleSequenceStrings()
+  {
+    HiddenColumns h = new HiddenColumns();
+    SequenceI seq1 = new Sequence("TEST1", "GALMFWKQESPVICYHRNDT");
+    SequenceI seq2 = new Sequence("TEST2", "VICYHRNDTGA");
+    SequenceI[] seqs = new SequenceI[2];
+    seqs[0] = seq1;
+    seqs[1] = seq2;
+    String[] result = h.getVisibleSequenceStrings(5, 10, seqs);
+    assertEquals(2, result.length);
+    assertEquals("WKQES", result[0]);
+    assertEquals("RNDTG", result[1]);
+  
+    h.hideColumns(6, 8);
+    result = h.getVisibleSequenceStrings(5, 10, seqs);
+    assertEquals(2, result.length);
+    assertEquals("WS", result[0]);
+    assertEquals("RG", result[1]);
+  
+    SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    h.hideColumns(1, 3);
+    h.hideColumns(6, 11);
+    assertEquals("-D",
+            h.getVisibleSequenceStrings(0, 5, new SequenceI[]
+    { seq })[0]);
+  }*/
+
+  @Test(groups = "Functional")
+  public void testHideInsertionsFor()
+  {
+    HiddenColumns h = new HiddenColumns();
+    HiddenColumns h2 = new HiddenColumns();
+    SequenceI seq1 = new Sequence("TEST1", "GAL---MFW-KQESPVICY--HRNDT");
+    SequenceI seq2 = new Sequence("TEST1", "GALMFWKQESPVICYHRNDT");
+
+    h.hideList(seq2.getInsertions());
+    assertTrue(h.equals(h2));
+    assertEquals(0, h.getSize());
+
+    h.hideList(seq1.getInsertions());
+    h2.hideColumns(3, 5);
+    h2.hideColumns(9, 9);
+    h2.hideColumns(19, 20);
+    assertTrue(h.equals(h2));
+    assertEquals(6, h.getSize());
+  }
+
+  @Test(groups = "Functional")
+  public void testHideColumns_BitSet_range()
+  {
+    HiddenColumns h = new HiddenColumns();
+    HiddenColumns h2 = new HiddenColumns();
+
+    BitSet tohide = new BitSet(25);
+    h.hideColumns(tohide);
+    assertTrue(h.equals(h2));
+
+    // when setting bitset, first param is inclusive, second exclusive
+    tohide.set(3, 6);
+    tohide.set(9);
+    tohide.set(15, 21);
+    h.clearAndHideColumns(tohide, 5, 23);
+
+    h2.hideColumns(5, 5);
+    h2.hideColumns(9, 9);
+    h2.hideColumns(15, 20);
+    assertTrue(h.equals(h2));
+    assertEquals(h.getSize(), h2.getSize());
+
+    tohide.clear();
+    tohide.set(41);
+    h.clearAndHideColumns(tohide, 23, 30);
+    assertTrue(h.equals(h2));
+    assertEquals(h.getSize(), h2.getSize());
+
+    tohide.set(41);
+    h.clearAndHideColumns(tohide, 30, 45);
+    h2.hideColumns(41, 41);
+    assertTrue(h.equals(h2));
+    assertEquals(h.getSize(), h2.getSize());
+
+    tohide.clear();
+    tohide.set(25, 28);
+    h.clearAndHideColumns(tohide, 17, 50);
+    h2 = new HiddenColumns();
+    h2.hideColumns(5, 5);
+    h2.hideColumns(9, 9);
+    h2.hideColumns(15, 16);
+    h2.hideColumns(25, 27);
+    assertTrue(h.equals(h2));
+    assertEquals(h.getSize(), h2.getSize());
+
+    HiddenColumns hc = new HiddenColumns();
+    hc.hideColumns(3, 5);
+    hc.hideColumns(15, 20);
+    hc.hideColumns(45, 60);
+
+    tohide = new BitSet();
+
+    // all unhidden if tohide is empty and range covers hidden
+    hc.clearAndHideColumns(tohide, 1, 70);
+    assertTrue(!hc.hasHiddenColumns());
+    assertEquals(0, hc.getSize());
+
+    hc.hideColumns(3, 5);
+    hc.hideColumns(15, 20);
+    hc.hideColumns(45, 60);
+    assertEquals(25, hc.getSize());
+
+    // but not if range does not cover hidden
+    hc.clearAndHideColumns(tohide, 23, 40);
+    assertTrue(hc.hasHiddenColumns());
+    assertEquals(25, hc.getSize());
+
+    // and partial unhide if range partially covers
+    hc.clearAndHideColumns(tohide, 1, 17);
+    Iterator<int[]> it = hc.iterator();
+    assertTrue(it.hasNext());
+    int[] region = it.next();
+
+    assertEquals(18, region[0]);
+    assertEquals(20, region[1]);
+
+    assertTrue(it.hasNext());
+    region = it.next();
+
+    assertEquals(45, region[0]);
+    assertEquals(60, region[1]);
+
+    assertFalse(it.hasNext());
+    assertEquals(19, hc.getSize());
+  }
+
+  @Test(groups = "Functional")
+  public void testOffsetByVisibleColumns()
+  {
+    HiddenColumns h = new HiddenColumns();
+    int result = h.offsetByVisibleColumns(-1, 10);
+    assertEquals(9, result);
+
+    h.hideColumns(7, 9);
+    result = h.offsetByVisibleColumns(-4, 10);
+    assertEquals(3, result);
+
+    h.hideColumns(14, 15);
+    result = h.offsetByVisibleColumns(-4, 10);
+    assertEquals(3, result);
+
+    result = h.offsetByVisibleColumns(-10, 17);
+    assertEquals(2, result);
+
+    result = h.offsetByVisibleColumns(-1, 7);
+    assertEquals(5, result);
+
+    result = h.offsetByVisibleColumns(-1, 8);
+    assertEquals(5, result);
+
+    result = h.offsetByVisibleColumns(-3, 15);
+    assertEquals(10, result);
+
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    h.hideColumns(0, 30);
+    result = h.offsetByVisibleColumns(-31, 0);
+    assertEquals(-31, result);
+
+    HiddenColumns cs = new HiddenColumns();
+
+    // test that without hidden columns, offsetByVisibleColumns returns
+    // position n to left of provided position
+    long pos = cs.offsetByVisibleColumns(-3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = cs.offsetByVisibleColumns(0, 10);
+    assertEquals(10, pos);
+
+    // overflow to left returns negative number
+    pos = cs.offsetByVisibleColumns(-3, 0);
+    assertEquals(-3, pos);
+
+    // test that with hidden columns to left of result column
+    // behaviour is the same as above
+    cs.hideColumns(1, 3);
+
+    // position n to left of provided position
+    pos = cs.offsetByVisibleColumns(-3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = cs.offsetByVisibleColumns(0, 10);
+    assertEquals(10, pos);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.offsetByVisibleColumns(-8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.offsetByVisibleColumns(-8, 23);
+    assertEquals(9, pos);
+
+    // repeat last 2 tests with no hidden columns to left of required position
+    ColumnSelection colsel = new ColumnSelection();
+    cs.revealAllHiddenColumns(colsel);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.offsetByVisibleColumns(-8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.offsetByVisibleColumns(-8, 23);
+    assertEquals(9, pos);
+
+    // test with right (positive) offsets
+
+    // test that without hidden columns, offsetByVisibleColumns returns
+    // position n to right of provided position
+    pos = cs.offsetByVisibleColumns(3, 7);
+    assertEquals(10, pos);
+
+    // test that with hidden columns to left of result column
+    // behaviour is the same as above
+    cs.hideColumns(1, 3);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.offsetByVisibleColumns(8, 5);
+    assertEquals(17, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.offsetByVisibleColumns(8, 9);
+    assertEquals(23, pos);
+
+    // repeat last 2 tests with no hidden columns to left of required position
+    colsel = new ColumnSelection();
+    cs.revealAllHiddenColumns(colsel);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.offsetByVisibleColumns(8, 5);
+    assertEquals(17, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.offsetByVisibleColumns(8, 9);
+    assertEquals(23, pos);
+  }
+
+  @Test(groups = "Functional")
+  public void testBoundedIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<int[]> it = h.getBoundedIterator(0, 10);
+
+    // no hidden columns = nothing to iterate over
+    assertFalse(it.hasNext());
+
+    // [start,end] contains all hidden columns
+    // all regions are returned
+    h.hideColumns(3, 10);
+    h.hideColumns(14, 16);
+    it = h.getBoundedIterator(0, 20);
+    assertTrue(it.hasNext());
+    int[] next = it.next();
+    assertEquals(3, next[0]);
+    assertEquals(10, next[1]);
+    next = it.next();
+    assertEquals(14, next[0]);
+    assertEquals(16, next[1]);
+    assertFalse(it.hasNext());
+
+    // [start,end] overlaps a region
+    // 1 region returned
+    it = h.getBoundedIterator(5, 7);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(3, next[0]);
+    assertEquals(10, next[1]);
+    assertFalse(it.hasNext());
+
+    // [start,end] fully contains 1 region and start of last
+    // - 2 regions returned
+    it = h.getBoundedIterator(3, 15);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(3, next[0]);
+    assertEquals(10, next[1]);
+    next = it.next();
+    assertEquals(14, next[0]);
+    assertEquals(16, next[1]);
+    assertFalse(it.hasNext());
+
+    // [start,end] contains end of first region and whole of last region
+    // - 2 regions returned
+    it = h.getBoundedIterator(4, 20);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(3, next[0]);
+    assertEquals(10, next[1]);
+    next = it.next();
+    assertEquals(14, next[0]);
+    assertEquals(16, next[1]);
+    assertFalse(it.hasNext());
+  }
+
+  @Test(groups = "Functional")
+  public void testBoundedStartIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<Integer> it = h.getStartRegionIterator(0, 10);
+
+    // no hidden columns = nothing to iterate over
+    assertFalse(it.hasNext());
+
+    // [start,end] contains all hidden columns
+    // all regions are returned
+    h.hideColumns(3, 10);
+    h.hideColumns(14, 16);
+    it = h.getStartRegionIterator(0, 20);
+    assertTrue(it.hasNext());
+    int next = it.next();
+    assertEquals(3, next);
+    next = it.next();
+    assertEquals(6, next);
+    assertFalse(it.hasNext());
+
+    // [start,end] does not contain a start of a region
+    // no regions to iterate over
+    it = h.getStartRegionIterator(4, 5);
+    assertFalse(it.hasNext());
+
+    // [start,end] fully contains 1 region and start of last
+    // - 2 regions returned
+    it = h.getStartRegionIterator(3, 7);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(3, next);
+    next = it.next();
+    assertEquals(6, next);
+    assertFalse(it.hasNext());
+
+    // [start,end] contains whole of last region
+    // - 1 region returned
+    it = h.getStartRegionIterator(4, 20);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(6, next);
+    assertFalse(it.hasNext());
+  }
+
+  @Test(groups = "Functional")
+  public void testVisibleBlocksVisBoundsIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<int[]> regions = h.getVisContigsIterator(0, 31, true);
+
+    // only 1 visible region spanning 0-30 if nothing is hidden
+    assertTrue(regions.hasNext());
+    int[] region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(30, region[1]);
+    assertFalse(regions.hasNext());
+
+    // hide 1 region in middle
+    // 2 regions one on either side
+    // second region boundary accounts for hidden columns
+    h.hideColumns(10, 15);
+    regions = h.getVisContigsIterator(0, 31, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(9, region[1]);
+    region = regions.next();
+    assertEquals(16, region[0]);
+    assertEquals(36, region[1]);
+    assertFalse(regions.hasNext());
+
+    // single hidden region at left
+    h = new HiddenColumns();
+    h.hideColumns(0, 5);
+    regions = h.getVisContigsIterator(0, 31, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(6, region[0]);
+    assertEquals(36, region[1]);
+    assertFalse(regions.hasNext());
+
+    // single hidden region at right
+    h = new HiddenColumns();
+    h.hideColumns(27, 30);
+    regions = h.getVisContigsIterator(0, 31, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(26, region[1]);
+    region = regions.next();
+    assertEquals(31, region[0]);
+    assertEquals(34, region[1]);
+    assertFalse(regions.hasNext());
+
+    // hidden region at left + hidden region in middle
+    h = new HiddenColumns();
+    h.hideColumns(0, 5);
+    h.hideColumns(23, 25);
+    regions = h.getVisContigsIterator(0, 31, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(6, region[0]);
+    assertEquals(22, region[1]);
+    region = regions.next();
+    assertEquals(26, region[0]);
+    assertEquals(39, region[1]);
+    assertFalse(regions.hasNext());
+
+    // hidden region at right + hidden region in middle
+    h = new HiddenColumns();
+    h.hideColumns(27, 30);
+    h.hideColumns(11, 14);
+    regions = h.getVisContigsIterator(0, 31, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(10, region[1]);
+    region = regions.next();
+    assertEquals(15, region[0]);
+    assertEquals(26, region[1]);
+    region = regions.next();
+    assertEquals(31, region[0]);
+    assertEquals(38, region[1]);
+    assertFalse(regions.hasNext());
+
+    // hidden region at left and right
+    h = new HiddenColumns();
+    h.hideColumns(27, 35);
+    h.hideColumns(0, 4);
+    regions = h.getVisContigsIterator(0, 31, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(5, region[0]);
+    assertEquals(26, region[1]);
+    region = regions.next();
+    assertEquals(36, region[0]);
+    assertEquals(44, region[1]);
+    assertFalse(regions.hasNext());
+
+    // multiple hidden regions
+    h = new HiddenColumns();
+    h.hideColumns(1, 1);
+    h.hideColumns(3, 5);
+    h.hideColumns(9, 11);
+    h.hideColumns(22, 26);
+
+    regions = h.getVisContigsIterator(0, 31, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(0, region[1]);
+    region = regions.next();
+    assertEquals(2, region[0]);
+    assertEquals(2, region[1]);
+    region = regions.next();
+    assertEquals(6, region[0]);
+    assertEquals(8, region[1]);
+    region = regions.next();
+    assertEquals(12, region[0]);
+    assertEquals(21, region[1]);
+    region = regions.next();
+    assertEquals(27, region[0]);
+    assertEquals(42, region[1]);
+    assertFalse(regions.hasNext());
+  }
+
+  /*
+   * the VisibleColsIterator is tested elsewhere, this just tests that 
+   * it can be retrieved from HiddenColumns
+   */
+  @Test(groups = "Functional")
+  public void testGetVisibleColsIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<Integer> it = h.getVisibleColsIterator(0, 10);
+
+    assertTrue(it instanceof RangeElementsIterator);
+  }
+
+  @Test(groups = "Functional")
+  public void testHashCode()
+  {
+    HiddenColumns h = new HiddenColumns();
+    h.hideColumns(0, 25);
+
+    int result = h.hashCode();
+    assertTrue(result > 0);
+
+    h.hideColumns(30, 50);
+    assertTrue(h.hashCode() > 0);
+    assertTrue(result != h.hashCode());
   }
 }
@@ -22,12 +22,13 @@ package jalview.datamodel;
 
 import static org.testng.Assert.assertTrue;
 
+import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
-public class VisibleColsIteratorTest
+public class RangeElementsIteratorTest
 {
   HiddenColumns hiddenCols;
 
@@ -50,11 +51,12 @@ public class VisibleColsIteratorTest
   @Test(groups = { "Functional" })
   public void testHasNextAndNextWithHidden()
   {
-    VisibleColsIterator it = new VisibleColsIterator(0, 6, hiddenCols);
+    Iterator<Integer> it = hiddenCols.getVisibleColsIterator(0, 6);
     int count = 0;
     while (it.hasNext())
     {
-      it.next();
+      int result = it.next();
+      System.out.println(result);
       count++;
     }
     assertTrue(count == 4, "hasNext() is false after 4 iterations");
@@ -67,8 +69,8 @@ public class VisibleColsIteratorTest
   @Test(groups = { "Functional" })
   public void testHasNextAndNextNoHidden()
   {
-    VisibleColsIterator it2 = new VisibleColsIterator(0, 3,
-            new HiddenColumns());
+    HiddenColumns test = new HiddenColumns();
+    Iterator<Integer> it2 = test.getVisibleColsIterator(0, 3);
     int count = 0;
     while (it2.hasNext())
     {
@@ -85,8 +87,7 @@ public class VisibleColsIteratorTest
   @Test(groups = { "Functional" })
   public void testHasNextAndNextStartHidden()
   {
-    VisibleColsIterator it3 = new VisibleColsIterator(0, 6,
-            hiddenColsAtStart);
+    Iterator<Integer> it3 = hiddenColsAtStart.getVisibleColsIterator(0, 6);
     int count = 0;
     while (it3.hasNext())
     {
@@ -103,7 +104,7 @@ public class VisibleColsIteratorTest
   @Test(groups = { "Functional" })
   public void testHasNextAndNextEndHidden()
   {
-    VisibleColsIterator it4 = new VisibleColsIterator(0, 4, hiddenCols);
+    Iterator<Integer> it4 = hiddenCols.getVisibleColsIterator(0, 4);
     int count = 0;
     while (it4.hasNext())
     {
@@ -123,7 +124,7 @@ public class VisibleColsIteratorTest
     expectedExceptions = { NoSuchElementException.class })
   public void testLastNextWithHidden() throws NoSuchElementException
   {
-    VisibleColsIterator it = new VisibleColsIterator(0, 3, hiddenCols);
+    Iterator<Integer> it = hiddenCols.getVisibleColsIterator(0, 3);
     while (it.hasNext())
     {
       it.next();
@@ -140,8 +141,8 @@ public class VisibleColsIteratorTest
     expectedExceptions = { NoSuchElementException.class })
   public void testLastNextNoHidden() throws NoSuchElementException
   {
-    VisibleColsIterator it2 = new VisibleColsIterator(0, 3,
-            new HiddenColumns());
+    HiddenColumns test = new HiddenColumns();
+    Iterator<Integer> it2 = test.getVisibleColsIterator(0, 3);
     while (it2.hasNext())
     {
       it2.next();
@@ -158,8 +159,7 @@ public class VisibleColsIteratorTest
     expectedExceptions = { NoSuchElementException.class })
   public void testLastNextStartHidden() throws NoSuchElementException
   {
-    VisibleColsIterator it3 = new VisibleColsIterator(0, 6,
-            hiddenColsAtStart);
+    Iterator<Integer> it3 = hiddenColsAtStart.getVisibleColsIterator(0, 6);
     while (it3.hasNext())
     {
       it3.next();
@@ -176,7 +176,7 @@ public class VisibleColsIteratorTest
     expectedExceptions = { NoSuchElementException.class })
   public void testLastNextEndHidden() throws NoSuchElementException
   {
-    VisibleColsIterator it4 = new VisibleColsIterator(0, 4, hiddenCols);
+    Iterator<Integer> it4 = hiddenCols.getVisibleColsIterator(0, 4);
     while (it4.hasNext())
     {
       it4.next();
@@ -192,7 +192,7 @@ public class VisibleColsIteratorTest
     expectedExceptions = { UnsupportedOperationException.class })
   public void testRemove() throws UnsupportedOperationException
   {
-    VisibleColsIterator it = new VisibleColsIterator(0, 3, hiddenCols);
+    Iterator<Integer> it = hiddenCols.getVisibleColsIterator(0, 3);
     it.remove();
   }
 }
index a084a8e..5a14514 100644 (file)
@@ -28,6 +28,7 @@ import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertSame;
 import static org.testng.AssertJUnit.assertTrue;
 
+import jalview.analysis.AlignmentGenerator;
 import jalview.commands.EditCommand;
 import jalview.commands.EditCommand.Action;
 import jalview.datamodel.PDBEntry.Type;
@@ -38,16 +39,17 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Vector;
 
-import junit.extensions.PA;
-
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 public class SequenceTest
 {
 
@@ -799,7 +801,7 @@ public class SequenceTest
     Assert.assertEquals(pdbe1a,
             sq.getDatasetSequence().getPDBEntry("1PDB"),
             "PDB Entry '1PDB' not found on dataset sequence via getPDBEntry.");
-    ArrayList<Annotation> annotsList = new ArrayList<Annotation>();
+    ArrayList<Annotation> annotsList = new ArrayList<>();
     System.out.println(">>>>>> " + sq.getSequenceAsString().length());
     annotsList.add(new Annotation("A", "A", 'X', 0.1f));
     annotsList.add(new Annotation("A", "A", 'X', 0.1f));
@@ -1674,6 +1676,20 @@ public class SequenceTest
   }
 
   @Test(groups = { "Functional" })
+  public void testGapBitset()
+  {
+    SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--");
+    BitSet bs = sq.gapBitset();
+    BitSet expected = new BitSet();
+    expected.set(0);
+    expected.set(4, 7);
+    expected.set(9);
+    expected.set(11, 13);
+
+    assertTrue(bs.equals(expected));
+
+  }
+
   public void testFindFeatures_largeEndPos()
   {
     /*
@@ -1881,4 +1897,162 @@ public class SequenceTest
     assertEquals(8, sq.getDatasetSequence().getStart());
     assertEquals(9, sq.getDatasetSequence().getEnd());
   }
+
+  /**
+   * Test the code used to locate the reference sequence ruler origin
+   */
+  @Test(groups = { "Functional" })
+  public void testLocateVisibleStartofSequence()
+  {
+    // create random alignment
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(50, 20, 123, 5, 5);
+
+    HiddenColumns cs = al.getHiddenColumns();
+    ColumnSelection colsel = new ColumnSelection();
+
+    SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
+    assertEquals(2, seq.findIndex(seq.getStart()));
+
+    // no hidden columns
+    assertEquals(seq.findIndex(seq.getStart()) - 1,
+            seq.firstResidueOutsideIterator(cs.iterator()));
+
+    // hidden column on gap after end of sequence - should not affect bounds
+    colsel.hideSelectedColumns(13, al.getHiddenColumns());
+    assertEquals(seq.findIndex(seq.getStart()) - 1,
+            seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    // hidden column on gap before beginning of sequence - should vis bounds by
+    // one
+    colsel.hideSelectedColumns(0, al.getHiddenColumns());
+    assertEquals(seq.findIndex(seq.getStart()) - 2,
+            cs.absoluteToVisibleColumn(
+                    seq.firstResidueOutsideIterator(cs.iterator())));
+
+    cs.revealAllHiddenColumns(colsel);
+    // hide columns around most of sequence - leave one residue remaining
+    cs.hideColumns(1, 3);
+    cs.hideColumns(6, 11);
+
+    Iterator<int[]> it = cs.getVisContigsIterator(0, 6, false);
+
+    assertEquals("-D", seq.getSequenceStringFromIterator(it));
+    // cs.getVisibleSequenceStrings(0, 5, new SequenceI[]
+    // { seq })[0]);
+
+    assertEquals(4, seq.firstResidueOutsideIterator(cs.iterator()));
+    cs.revealAllHiddenColumns(colsel);
+
+    // hide whole sequence - should just get location of hidden region
+    // containing sequence
+    cs.hideColumns(1, 11);
+    assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 15);
+    assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    SequenceI seq2 = new Sequence("RefSeq2", "-------A-SD-ASD--E---");
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(7, 17);
+    assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(3, 17);
+    assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(3, 19);
+    assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 0);
+    assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 1);
+    assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 2);
+    assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(1, 1);
+    assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(1, 2);
+    assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(1, 3);
+    assertEquals(4, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 2);
+    cs.hideColumns(5, 6);
+    assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 2);
+    cs.hideColumns(5, 6);
+    cs.hideColumns(9, 10);
+    assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 2);
+    cs.hideColumns(7, 11);
+    assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(2, 4);
+    cs.hideColumns(7, 11);
+    assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(2, 4);
+    cs.hideColumns(7, 12);
+    assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(1, 11);
+    assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 12);
+    assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 4);
+    cs.hideColumns(6, 12);
+    assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 1);
+    cs.hideColumns(3, 12);
+    assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(3, 14);
+    cs.hideColumns(17, 19);
+    assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(3, 7);
+    cs.hideColumns(9, 14);
+    cs.hideColumns(17, 19);
+    assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 1);
+    cs.hideColumns(3, 4);
+    cs.hideColumns(6, 8);
+    cs.hideColumns(10, 12);
+    assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
+
+  }
 }
diff --git a/test/jalview/datamodel/StartRegionIteratorTest.java b/test/jalview/datamodel/StartRegionIteratorTest.java
new file mode 100644 (file)
index 0000000..23d0b00
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.datamodel;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+public class StartRegionIteratorTest
+{
+  /**
+   * Test the start region iterator
+   */
+  @Test(groups = { "Functional" })
+  public void testBasicBoundsIterator()
+  {
+    List<int[]> hiddenColumns = null;
+
+    // null hidden columns
+    Iterator<Integer> it = new StartRegionIterator(3, 10,
+            hiddenColumns);
+    assertFalse(it.hasNext());
+
+    hiddenColumns = new ArrayList<>();
+
+    // no hidden columns
+    it = new StartRegionIterator(3, 10, hiddenColumns);
+    assertFalse(it.hasNext());
+
+    // add some hidden columns
+    hiddenColumns.add(new int[] { 5, 10 });
+    hiddenColumns.add(new int[] { 25, 40 });
+
+    it = new StartRegionIterator(3, 10, hiddenColumns);
+    assertTrue(it.hasNext());
+    Integer result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(3, 15, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(3, 18, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(3, 19, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(19, (int) result);
+    assertFalse(it.hasNext());
+
+    hiddenColumns.add(new int[] { 47, 50 });
+
+    it = new StartRegionIterator(15, 60, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(19, (int) result);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(25, (int) result);
+    assertFalse(it.hasNext());
+  }
+
+  /**
+   * Test the start region iterator with null cursor
+   */
+  @Test(groups = { "Functional" })
+  public void testBoundsIteratorUsingNullCursor()
+  {
+    List<int[]> hiddenColumns = null;
+    HiddenCursorPosition pos = null;
+
+    // null hidden columns
+    Iterator<Integer> it = new StartRegionIterator(pos, 3, 10,
+            hiddenColumns);
+    assertFalse(it.hasNext());
+
+    hiddenColumns = new ArrayList<>();
+
+    // no hidden columns
+    it = new StartRegionIterator(pos, 3, 10, hiddenColumns);
+    assertFalse(it.hasNext());
+
+    // add some hidden columns
+    hiddenColumns.add(new int[] { 5, 10 });
+    hiddenColumns.add(new int[] { 25, 40 });
+
+    it = new StartRegionIterator(pos, 3, 10, hiddenColumns);
+    assertTrue(it.hasNext());
+    Integer result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(pos, 3, 15, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(pos, 3, 18, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(pos, 3, 19, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(19, (int) result);
+    assertFalse(it.hasNext());
+
+    hiddenColumns.add(new int[] { 47, 50 });
+
+    it = new StartRegionIterator(pos, 15, 60, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(19, (int) result);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(25, (int) result);
+    assertFalse(it.hasNext());
+  }
+
+  /**
+   * Test the start region iterator with nonnull cursor
+   */
+  @Test(groups = { "Functional" })
+  public void testBoundsIteratorUsingCursor()
+  {
+    List<int[]> hiddenColumns = new ArrayList<>();
+
+    // add some hidden columns
+    hiddenColumns.add(new int[] { 5, 10 });
+    hiddenColumns.add(new int[] { 25, 40 });
+
+    HiddenCursorPosition pos = new HiddenCursorPosition(0, 0);
+
+    Iterator<Integer> it = new StartRegionIterator(pos, 3, 10,
+            hiddenColumns);
+    assertTrue(it.hasNext());
+    Integer result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(pos, 3, 15, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(pos, 3, 18, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertFalse(it.hasNext());
+
+    it = new StartRegionIterator(pos, 3, 19, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(5, (int) result);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(19, (int) result);
+    assertFalse(it.hasNext());
+
+    pos = new HiddenCursorPosition(1, 6);
+    hiddenColumns.add(new int[] { 47, 50 });
+
+    it = new StartRegionIterator(pos, 15, 60, hiddenColumns);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(19, (int) result);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(25, (int) result);
+    assertFalse(it.hasNext());
+  }
+}
diff --git a/test/jalview/datamodel/VisibleContigsIteratorTest.java b/test/jalview/datamodel/VisibleContigsIteratorTest.java
new file mode 100644 (file)
index 0000000..8f31dae
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.datamodel;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+public class VisibleContigsIteratorTest
+{
+  /**
+   * Test the iterator with single visible regions
+   */
+  @Test(groups = { "Functional" })
+  public void testSimpleVisibleRegions()
+  {
+    List<int[]> hiddenColumns = null;
+
+    // null hidden columns
+    VisibleContigsIterator it = new VisibleContigsIterator(3, 10,
+            hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    int[] result = it.next();
+    assertEquals(3, result[0]);
+    assertEquals(9, result[1]);
+    assertFalse(it.hasNext());
+    assertFalse(it.endsAtHidden());
+
+    hiddenColumns = new ArrayList<>();
+
+    // no hidden columns
+    it = new VisibleContigsIterator(3, 10,
+            hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(3, result[0]);
+    assertEquals(9, result[1]);
+    assertFalse(it.hasNext());
+    assertFalse(it.endsAtHidden());
+
+    // hidden columns, but not where we are looking
+    hiddenColumns.add(new int[] { 5, 10 });
+    hiddenColumns.add(new int[] { 25, 40 });
+
+    it = new VisibleContigsIterator(2, 3, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(2, result[0]);
+    assertEquals(2, result[1]);
+    assertFalse(it.hasNext());
+    assertFalse(it.endsAtHidden());
+
+    it = new VisibleContigsIterator(5, 7, hiddenColumns);
+    assertFalse(it.hasNext());
+    assertFalse(it.endsAtHidden());
+
+    it = new VisibleContigsIterator(11, 15, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(11, result[0]);
+    assertEquals(14, result[1]);
+    assertFalse(it.hasNext());
+    assertFalse(it.endsAtHidden());
+
+    it = new VisibleContigsIterator(50, 60, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(50, result[0]);
+    assertEquals(59, result[1]);
+    assertFalse(it.hasNext());
+    assertFalse(it.endsAtHidden());
+  }
+
+  /**
+   * Test the iterator with multiple visible regions
+   */
+  @Test(groups = { "Functional" })
+  public void testMultipleVisibleRegions()
+  {
+    List<int[]> hiddenColumns = new ArrayList<>();
+    hiddenColumns.add(new int[] { 5, 10 });
+    hiddenColumns.add(new int[] { 25, 40 });
+
+    // all hidden columns covered
+    VisibleContigsIterator it = new VisibleContigsIterator(3, 50,
+            hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    int[] result = it.next();
+    assertEquals(3, result[0]);
+    assertEquals(4, result[1]);
+
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(11, result[0]);
+    assertEquals(24, result[1]);
+
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(41, result[0]);
+    assertEquals(49, result[1]);
+
+    assertFalse(it.hasNext());
+    assertFalse(it.endsAtHidden());
+  }
+
+  /**
+   * Test the iterator with regions which start/end at hidden region edges
+   */
+  @Test(groups = { "Functional" })
+  public void testVisibleRegionsAtHiddenEdges()
+  {
+    List<int[]> hiddenColumns = new ArrayList<>();
+    hiddenColumns.add(new int[] { 5, 10 });
+    hiddenColumns.add(new int[] { 25, 40 });
+
+    VisibleContigsIterator it = new VisibleContigsIterator(0, 10,
+            hiddenColumns);
+    assertTrue(it.hasNext());
+    assertTrue(it.endsAtHidden());
+    int[] result = it.next();
+    assertEquals(0, result[0]);
+    assertEquals(4, result[1]);
+    assertFalse(it.hasNext());
+    assertTrue(it.endsAtHidden());
+
+    it = new VisibleContigsIterator(2, 11, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertTrue(it.endsAtHidden());
+    result = it.next();
+    assertEquals(2, result[0]);
+    assertEquals(4, result[1]);
+    assertFalse(it.hasNext());
+    assertTrue(it.endsAtHidden());
+
+    it = new VisibleContigsIterator(2, 12, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(2, result[0]);
+    assertEquals(4, result[1]);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(11, result[0]);
+    assertEquals(11, result[1]);
+    assertFalse(it.hasNext());
+    assertFalse(it.endsAtHidden());
+
+    it = new VisibleContigsIterator(13, 25, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(13, result[0]);
+    assertEquals(24, result[1]);
+    assertFalse(it.hasNext());
+
+    it = new VisibleContigsIterator(13, 26, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertTrue(it.endsAtHidden());
+    result = it.next();
+    assertEquals(13, result[0]);
+    assertEquals(24, result[1]);
+    assertFalse(it.hasNext());
+
+    it = new VisibleContigsIterator(13, 27, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertTrue(it.endsAtHidden());
+    result = it.next();
+    assertEquals(13, result[0]);
+    assertEquals(24, result[1]);
+    assertFalse(it.hasNext());
+
+    it = new VisibleContigsIterator(13, 41, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertTrue(it.endsAtHidden());
+    result = it.next();
+    assertEquals(13, result[0]);
+    assertEquals(24, result[1]);
+    assertFalse(it.hasNext());
+
+    it = new VisibleContigsIterator(13, 42, hiddenColumns);
+    assertTrue(it.hasNext());
+    assertFalse(it.endsAtHidden());
+    result = it.next();
+    assertEquals(13, result[0]);
+    assertEquals(24, result[1]);
+    assertTrue(it.hasNext());
+    result = it.next();
+    assertEquals(41, result[0]);
+    assertEquals(41, result[1]);
+  }
+}
index af9c045..dd1a4de 100644 (file)
@@ -46,7 +46,7 @@ import jalview.schemes.TurnColourScheme;
 import jalview.util.MessageManager;
 
 import java.awt.Color;
-import java.util.List;
+import java.util.Iterator;
 
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
@@ -85,24 +85,20 @@ public class AlignFrameTest
      */
     assertFalse(alignFrame.hideFeatureColumns("exon", true));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenColumnsCopy()
-            .isEmpty());
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 0);
     assertFalse(alignFrame.hideFeatureColumns("exon", false));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenColumnsCopy()
-            .isEmpty());
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 0);
 
     /*
      * hiding a feature in all columns does nothing
      */
     assertFalse(alignFrame.hideFeatureColumns("Metal", true));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    List<int[]> hidden = alignFrame.getViewport().getAlignment()
-            .getHiddenColumns()
-            .getHiddenColumnsCopy();
-    assertTrue(hidden.isEmpty());
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 0);
 
     /*
      * hide a feature present in some columns
@@ -110,13 +106,16 @@ public class AlignFrameTest
      * [1-3], [6-8] base zero
      */
     assertTrue(alignFrame.hideFeatureColumns("Turn", true));
-    hidden = alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenColumnsCopy();
-    assertEquals(hidden.size(), 2);
-    assertEquals(hidden.get(0)[0], 1);
-    assertEquals(hidden.get(0)[1], 3);
-    assertEquals(hidden.get(1)[0], 6);
-    assertEquals(hidden.get(1)[1], 8);
+    Iterator<int[]> regions = alignFrame.getViewport().getAlignment()
+            .getHiddenColumns().iterator();
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 2);
+    int[] next = regions.next();
+    assertEquals(next[0], 1);
+    assertEquals(next[1], 3);
+    next = regions.next();
+    assertEquals(next[0], 6);
+    assertEquals(next[1], 8);
   }
 
   @BeforeClass(alwaysRun = true)
index 06478d5..912cd27 100644 (file)
@@ -35,7 +35,7 @@ import jalview.io.FileFormat;
 import jalview.io.FormatAdapter;
 
 import java.io.IOException;
-import java.util.List;
+import java.util.Iterator;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
@@ -141,18 +141,21 @@ public class AnnotationColumnChooserTest
 
     HiddenColumns currentHidden = af.getViewport().getAlignment()
             .getHiddenColumns();
-    List<int[]> regions = currentHidden.getHiddenColumnsCopy();
-    assertEquals(regions.get(0)[0], 0);
-    assertEquals(regions.get(0)[1], 3);
-    assertEquals(regions.get(1)[0], 22);
-    assertEquals(regions.get(1)[1], 25);
+    Iterator<int[]> regions = currentHidden.iterator();
+    int[] next = regions.next();
+    assertEquals(0, next[0]);
+    assertEquals(3, next[1]);
+    next = regions.next();
+    assertEquals(22, next[0]);
+    assertEquals(25, next[1]);
 
     // now reset hidden columns
     acc.reset();
     currentHidden = af.getViewport().getAlignment().getHiddenColumns();
-    regions = currentHidden.getHiddenColumnsCopy();
-    assertEquals(regions.get(0)[0], 10);
-    assertEquals(regions.get(0)[1], 20);
+    regions = currentHidden.iterator();
+    next = regions.next();
+    assertEquals(10, next[0]);
+    assertEquals(20, next[1]);
 
     // check works with empty hidden columns as old columns
     oldhidden = new HiddenColumns();
@@ -169,8 +172,9 @@ public class AnnotationColumnChooserTest
 
     acc.reset();
     currentHidden = af.getViewport().getAlignment().getHiddenColumns();
-    regions = currentHidden.getHiddenColumnsCopy();
-    assertEquals(regions.get(0)[0], 10);
-    assertEquals(regions.get(0)[1], 20);
+    regions = currentHidden.iterator();
+    next = regions.next();
+    assertEquals(10, next[0]);
+    assertEquals(20, next[1]);
   }
 }
index 335240b..8f60021 100644 (file)
@@ -29,9 +29,12 @@ import static org.testng.AssertJUnit.assertTrue;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.DBRefSource;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
@@ -41,6 +44,7 @@ import jalview.util.MessageManager;
 import java.awt.Component;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 import javax.swing.JMenu;
@@ -100,7 +104,7 @@ public class PopupMenuTest
   public void testConfigureReferenceAnnotationsMenu_noSequenceSelected()
   {
     JMenuItem menu = new JMenuItem();
-    List<SequenceI> seqs = new ArrayList<SequenceI>();
+    List<SequenceI> seqs = new ArrayList<>();
     testee.configureReferenceAnnotationsMenu(menu, seqs);
     assertFalse(menu.isEnabled());
     // now try null list
@@ -469,8 +473,8 @@ public class PopupMenuTest
     List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
 
     // create list of links and list of DBRefs
-    List<String> links = new ArrayList<String>();
-    List<DBRefEntry> refs = new ArrayList<DBRefEntry>();
+    List<String> links = new ArrayList<>();
+    List<DBRefEntry> refs = new ArrayList<>();
 
     // links as might be added into Preferences | Connections dialog
     links.add("EMBL-EBI Search | http://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$"
@@ -548,7 +552,7 @@ public class PopupMenuTest
     }
 
     // if there are no valid links the Links submenu is disabled
-    List<String> nomatchlinks = new ArrayList<String>();
+    List<String> nomatchlinks = new ArrayList<>();
     nomatchlinks.add("NOMATCH | http://www.uniprot.org/uniprot/$"
             + DB_ACCESSION + "$");
 
@@ -559,4 +563,117 @@ public class PopupMenuTest
     assertFalse(linkMenu.isEnabled());
 
   }
+
+  /**
+   * Test for adding feature links
+   */
+  @Test(groups = { "Functional" })
+  public void testHideInsertions()
+  {
+    // get sequences from the alignment
+    List<SequenceI> seqs = parentPanel.getAlignment().getSequences();
+    
+    // add our own seqs to avoid problems with changes to existing sequences
+    // (gap at end of sequences varies depending on how tests are run!)
+    Sequence seqGap1 = new Sequence("GappySeq",
+            "AAAA----AA-AAAAAAA---AAA-----------AAAAAAAAAA--");
+    seqGap1.createDatasetSequence();
+    seqs.add(seqGap1);
+    Sequence seqGap2 = new Sequence("LessGappySeq",
+            "AAAAAA-AAAAA---AAA--AAAAA--AAAAAAA-AAAAAA");
+    seqGap2.createDatasetSequence();
+    seqs.add(seqGap2);
+    Sequence seqGap3 = new Sequence("AnotherGapSeq",
+            "AAAAAA-AAAAAA--AAAAAA-AAAAAAAAAAA---AAAAAAAA");
+    seqGap3.createDatasetSequence();
+    seqs.add(seqGap3);
+    Sequence seqGap4 = new Sequence("NoGaps",
+            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
+    seqGap4.createDatasetSequence();
+    seqs.add(seqGap4);
+
+    ColumnSelection sel = new ColumnSelection();
+    parentPanel.av.getAlignment().getHiddenColumns()
+            .revealAllHiddenColumns(sel);
+
+    // get the Popup Menu for 7th sequence - no insertions
+    testee = new PopupMenu(parentPanel, (Sequence) seqs.get(7), null);
+    testee.hideInsertions_actionPerformed(null);
+    
+    HiddenColumns hidden = parentPanel.av.getAlignment().getHiddenColumns();
+    Iterator<int[]> it = hidden.iterator();
+    assertFalse(it.hasNext());
+
+    // get the Popup Menu for GappySeq - this time we have insertions
+    testee = new PopupMenu(parentPanel, (Sequence) seqs.get(4), null);
+    testee.hideInsertions_actionPerformed(null);
+    hidden = parentPanel.av.getAlignment().getHiddenColumns();
+    it = hidden.iterator();
+
+    assertTrue(it.hasNext());
+    int[] region = it.next();
+    assertEquals(region[0], 4);
+    assertEquals(region[1], 7);
+
+    assertTrue(it.hasNext());
+    region = it.next();
+    assertEquals(region[0], 10);
+    assertEquals(region[1], 10);
+
+    assertTrue(it.hasNext());
+    region = it.next();
+    assertEquals(region[0], 18);
+    assertEquals(region[1], 20);
+
+    assertTrue(it.hasNext());
+    region = it.next();
+    assertEquals(region[0], 24);
+    assertEquals(region[1], 34);
+
+    assertTrue(it.hasNext());
+    region = it.next();
+    assertEquals(region[0], 45);
+    assertEquals(region[1], 46);
+
+    assertFalse(it.hasNext());
+
+    sel = new ColumnSelection();
+    hidden.revealAllHiddenColumns(sel);
+
+    // make a sequence group and hide insertions within the group
+    SequenceGroup sg = new SequenceGroup();
+    sg.setStartRes(8);
+    sg.setEndRes(42);
+    sg.addSequence(seqGap2, false);
+    sg.addSequence(seqGap3, false);
+    parentPanel.av.setSelectionGroup(sg);
+
+    // hide columns outside and within selection
+    // only hidden columns outside the collection will be retained (unless also
+    // gaps in the selection)
+    hidden.hideColumns(1, 10);
+    hidden.hideColumns(31, 40);
+
+    // get the Popup Menu for LessGappySeq in the sequence group
+    testee = new PopupMenu(parentPanel, (Sequence) seqs.get(5), null);
+    testee.hideInsertions_actionPerformed(null);
+    hidden = parentPanel.av.getAlignment().getHiddenColumns();
+    it = hidden.iterator();
+
+    assertTrue(it.hasNext());
+    region = it.next();
+    assertEquals(region[0], 1);
+    assertEquals(region[1], 7);
+
+    assertTrue(it.hasNext());
+    region = it.next();
+    assertEquals(region[0], 13);
+    assertEquals(region[1], 14);
+
+    assertTrue(it.hasNext());
+    region = it.next();
+    assertEquals(region[0], 34);
+    assertEquals(region[1], 34);
+  }
+
 }
index 158c901..5e835bf 100644 (file)
@@ -42,6 +42,7 @@ import jalview.schemes.ResidueColourScheme;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -205,7 +206,7 @@ public class JSONFileTest
     TEST_SEQ_HEIGHT = expectedSeqs.size();
     TEST_GRP_HEIGHT = expectedGrps.size();
     TEST_ANOT_HEIGHT = expectedAnnots.size();
-    TEST_CS_HEIGHT = expectedColSel.getHiddenColumnsCopy().size();
+    TEST_CS_HEIGHT = expectedColSel.getNumberOfRegions();
 
     exportSettings = new AlignExportSettingI()
     {
@@ -325,11 +326,12 @@ public class JSONFileTest
   {
     HiddenColumns cs = testJsonFile.getHiddenColumns();
     Assert.assertNotNull(cs);
-    Assert.assertNotNull(cs.getHiddenColumnsCopy());
-    List<int[]> hiddenCols = cs.getHiddenColumnsCopy();
-    Assert.assertEquals(hiddenCols.size(), TEST_CS_HEIGHT);
-    Assert.assertEquals(hiddenCols.get(0), expectedColSel
-            .getHiddenColumnsCopy().get(0),
+
+    Iterator<int[]> it = cs.iterator();
+    Iterator<int[]> colselit = expectedColSel.iterator();
+    Assert.assertTrue(it.hasNext());
+    Assert.assertEquals(cs.getNumberOfRegions(), TEST_CS_HEIGHT);
+    Assert.assertEquals(it.next(), colselit.next(),
             "Mismatched hidden columns!");
   }
 
index 5226819..022e2d6 100644 (file)
@@ -50,6 +50,7 @@ import java.awt.Color;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 
 import org.testng.annotations.BeforeClass;
@@ -913,9 +914,9 @@ public class MappingUtilsTest
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
     assertEquals("[]", dnaSelection.getSelected().toString());
-    List<int[]> hidden = dnaHidden.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[0, 4]", Arrays.toString(hidden.get(0)));
+    Iterator<int[]> regions = dnaHidden.iterator();
+    assertEquals(1, dnaHidden.getNumberOfRegions());
+    assertEquals("[0, 4]", Arrays.toString(regions.next()));
 
     /*
      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
@@ -930,9 +931,9 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(1, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    hidden = dnaHidden.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
+    regions = dnaHidden.iterator();
+    assertEquals(1, dnaHidden.getNumberOfRegions());
+    assertEquals("[0, 3]", Arrays.toString(regions.next()));
 
     /*
      * Column 2 in protein picks up gaps only - no mapping
@@ -944,7 +945,7 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(2, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    assertTrue(dnaHidden.getHiddenColumnsCopy().isEmpty());
+    assertEquals(0, dnaHidden.getNumberOfRegions());
 
     /*
      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
@@ -959,9 +960,9 @@ public class MappingUtilsTest
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
     assertEquals("[0, 1, 2, 3]", dnaSelection.getSelected().toString());
-    hidden = dnaHidden.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[5, 10]", Arrays.toString(hidden.get(0)));
+    regions = dnaHidden.iterator();
+    assertEquals(1, dnaHidden.getNumberOfRegions());
+    assertEquals("[5, 10]", Arrays.toString(regions.next()));
 
     /*
      * Combine hiding columns 1 and 3 to get discontiguous hidden columns
@@ -974,10 +975,10 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(3, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    hidden = dnaHidden.getHiddenColumnsCopy();
-    assertEquals(2, hidden.size());
-    assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
-    assertEquals("[5, 10]", Arrays.toString(hidden.get(1)));
+    regions = dnaHidden.iterator();
+    assertEquals(2, dnaHidden.getNumberOfRegions());
+    assertEquals("[0, 3]", Arrays.toString(regions.next()));
+    assertEquals("[5, 10]", Arrays.toString(regions.next()));
   }
 
   @Test(groups = { "Functional" })