JAL-2839 refactored analysis.Finder and tests
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 13 Mar 2018 15:13:11 +0000 (15:13 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 13 Mar 2018 15:13:11 +0000 (15:13 +0000)
src/jalview/analysis/Finder.java
src/jalview/appletgui/Finder.java
src/jalview/appletgui/IdPanel.java
src/jalview/gui/Finder.java
src/jalview/jbgui/GFinder.java
test/jalview/analysis/AlignSeqTest.java
test/jalview/analysis/FinderTest.java

index d7bf0a2..de57d69 100644 (file)
@@ -26,8 +26,10 @@ import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.VisibleContigsIterator;
 import jalview.util.Comparison;
 
+import java.util.List;
 import java.util.Vector;
 
 import com.stevesoft.pat.Regex;
@@ -38,7 +40,7 @@ import com.stevesoft.pat.Regex;
 public class Finder
 {
   /*
-   * match residue locations
+   * matched residue locations
    */
   private SearchResultsI searchResults;
 
@@ -75,13 +77,14 @@ public class Finder
   /*
    * sequence index in alignment to search from
    */
-  private int seqIndex;
+  private int sequenceIndex;
 
   /*
-   * residue position in sequence to search from, base 1
+   * column position in sequence to search from, base 0
+   * - absolute column number including any hidden columns
    * (position of last match for a repeat search)
    */
-  private int resIndex;
+  private int columnIndex;
 
   /**
    * Constructor to start searching an alignment, optionally restricting results
@@ -102,15 +105,15 @@ public class Finder
    * @param al
    * @param sel
    * @param seqindex
-   * @param resindex
+   * @param colindex
    */
   public Finder(AlignmentI al, SequenceGroup sel, int seqindex,
-          int resindex)
+          int colindex)
   {
     this.alignment = al;
     this.selection = sel;
-    this.seqIndex = seqindex;
-    this.resIndex = resindex;
+    this.sequenceIndex = seqindex;
+    this.columnIndex = colindex;
   }
 
   /**
@@ -124,10 +127,16 @@ public class Finder
    */
   public void find(String theSearchString)
   {
-    String searchString = caseSensitive ? theSearchString.toUpperCase()
-            : theSearchString;
-    Regex regex = new Regex(searchString);
-    regex.setIgnoreCase(!caseSensitive);
+    if (findAll)
+    {
+      sequenceIndex = 0;
+      columnIndex = -1;
+    }
+
+    String searchString = caseSensitive ? theSearchString
+            : theSearchString.toUpperCase();
+    Regex searchPattern = new Regex(searchString);
+    searchPattern.setIgnoreCase(!caseSensitive);
     searchResults = new SearchResults();
     idMatch = new Vector<>();
 
@@ -136,115 +145,206 @@ public class Finder
       selection = null; // ? ignore column-only selection
     }
 
-    boolean finished = false;
     int end = alignment.getHeight();
 
-    while (!finished && (seqIndex < end))
+    while (sequenceIndex < end)
     {
-      SequenceI seq = alignment.getSequenceAt(seqIndex);
-
-      if ((selection != null) && !selection.contains(seq))
+      SequenceI seq = alignment.getSequenceAt(sequenceIndex);
+      boolean found = findNext(seq, searchString, searchPattern);
+      if (found && !findAll)
       {
-        // this sequence is not in the selection - skip to next sequence
-        seqIndex++;
-        resIndex = -1;
-        continue;
+        return;
       }
-
-      if (resIndex < 0)
+      if (!found)
       {
-        /*
-         * at start of sequence; try find by residue number, in sequence id,
-         * or (optionally) in sequence description
-         */
-        resIndex = 0;
-        if (doNonMotifSearches(seq, searchString, regex))
-        {
-          return;
-        }
+        sequenceIndex++;
+        columnIndex = -1;
       }
+    }
+  }
+
+  /**
+   * Answers the start-end column range of the visible region starting at or
+   * after the given column. if there are no hidden columns, this just returns
+   * the remaining width of the alignment. Answers null if there are no visible
+   * columns at or after <code>column</code>.
+   */
+  protected int[] getNextVisibleRegion(int column)
+  {
+    VisibleContigsIterator visibleRegions = alignment.getHiddenColumns()
+            .getVisContigsIterator(column, alignment.getWidth(),
+                    false);
+    return visibleRegions.hasNext() ? visibleRegions.next() : null;
+  }
+
+  /**
+   * Finds the next match in the given sequence, starting at column at
+   * <code>columnIndex</code>. Answers true if a match is found, else false. If
+   * a match is found, <code>columnIndex</code> is advanced to the column after
+   * the start of the matched region, ready for a search from the next position.
+   * 
+   * @param seq
+   * @param searchString
+   * @param searchPattern
+   * @return
+   */
+  protected boolean findNext(SequenceI seq, String searchString,
+          Regex searchPattern)
+  {
+    if (selection != null && !selection.contains(seq))
+    {
+      /*
+       * this sequence is not in the selection - advance to next sequence
+       */
+      return false;
+    }
 
-      finished = searchSequenceString(seq, regex) && !findAll;
+    if (columnIndex < 0)
+    {
+      /*
+       * at start of sequence; try find by residue number, in sequence id,
+       * or (optionally) in sequence description
+       */
+      if (doNonMotifSearches(seq, searchString, searchPattern))
+      {
+        return true;
+      }
+    }
 
-      if (!finished)
+    /*
+     * search for next match in sequence string
+     */
+    int end = seq.getLength();
+    while (columnIndex < end)
+    {
+      if (searchNextVisibleRegion(seq, searchPattern))
       {
-        seqIndex++;
-        resIndex = -1;
+        return true;
       }
     }
+    return false;
   }
 
   /**
-   * Searches the sequence, starting from <code>resIndex</code> (base 1), and
-   * adds matches to <code>searchResults</code>. The search is restricted to the
-   * <code>selection</code> region if there is one. Answers true if any match is
-   * added, else false.
+   * Searches the sequence, starting from <code>columnIndex</code>, and adds the
+   * next match (if any) to <code>searchResults</code>. The search is restricted
+   * to the next visible column region, and to the <code>selection</code> region
+   * if there is one. Answers true if a match is added, else false.
    * 
    * @param seq
-   * @param regex
+   * @param searchPattern
    * @return
    */
-  protected boolean searchSequenceString(SequenceI seq, Regex regex)
+  protected boolean searchNextVisibleRegion(SequenceI seq, Regex searchPattern)
   {
     /*
-     * Restrict search to selected region if there is one
+     * sequence columns to search (working in absolute column 
+     * positions, base 0, including any hidden columns)
      */
-    int seqColStart = 0;
+    int seqColStart = columnIndex;
     int seqColEnd = seq.getLength() - 1;
-    int residueOffset = 0;
+
+    /*
+     * restrict search to (next) visible column region, 
+     * in case there are hidden columns
+     */
+    int[] visible = getNextVisibleRegion(columnIndex);
+    if (visible != null)
+    {
+      seqColStart = Math.max(seqColStart, visible[0]);
+      seqColEnd = Math.min(seqColEnd, visible[1]);
+    }
+    else
+    {
+      columnIndex = seqColEnd + 1;
+      return false;
+    }
+
+    /*
+     * restrict search to selected region if there is one
+     */
     if (selection != null)
     {
-      int selColEnd = selection.getEndRes();
-      int selColStart = selection.getStartRes();
-      if (selColStart > seqColEnd)
+      int selectionStart = selection.getStartRes();
+      int selectionEnd = selection.getEndRes();
+      if (selectionStart > seqColEnd || selectionEnd < seqColStart)
       {
-        return false; // sequence doesn't reach selection region
+        /*
+         * sequence region doesn't overlap selection region - 
+         * no match, advance to next visible region 
+         */
+        columnIndex = seqColEnd + 1;
+        return false;
       }
-      seqColStart = selColStart;
-      seqColEnd = Math.min(seqColEnd, selColEnd);
-      residueOffset = seq.findPosition(selection.getStartRes())
-              - seq.getStart();
+      seqColStart = Math.max(seqColStart, selectionStart);
+      seqColEnd = Math.min(seqColEnd, selectionEnd);
     }
-    String seqString = seq.getSequenceAsString(seqColStart, seqColEnd + 1);
 
+    String seqString = seq.getSequenceAsString(seqColStart, seqColEnd + 1);
     String noGaps = AlignSeq.extractGaps(Comparison.GapChars, seqString);
 
-    SearchResultMatchI lastMatch = null;
-    boolean found = false;
-
-    for (int r = resIndex; r < noGaps.length(); r++)
+    if (searchPattern.search(noGaps))
+    {
+      int sequenceStartPosition = seq.findPosition(seqColStart);
+      recordMatch(seq, searchPattern, sequenceStartPosition);
+      return true;
+    }
+    else
     {
       /*
-       * searchFrom position is base 0, r is base 1, 
-       * so search is from the position after the r'th residue
+       * no match - advance columnIndex past this visible region
+       * so the next visible region (if any) is searched next
        */
-      if (regex.searchFrom(noGaps, r))
-      {
-        resIndex = regex.matchedFrom();
-        resIndex += residueOffset; // add back #residues before selection region
-        int matchStartPosition = resIndex + seq.getStart();
-        int matchEndPosition = matchStartPosition + regex.charsMatched()
-                - 1;
-        if (lastMatch == null || !lastMatch.contains(seq,
-                matchStartPosition, matchEndPosition))
-        {
-          lastMatch = searchResults.addResult(seq, matchStartPosition,
-                  matchEndPosition);
-          found = true;
-        }
-        if (!findAll)
-        {
-          resIndex++;
-          return true;
-        }
-        r = resIndex;
-      }
-      else
-      {
-        break;
-      }
+      columnIndex = seqColEnd + 1;
+    }
+
+    return false;
+  }
+
+  /**
+   * Adds the match held in the <code>searchPattern</code> Regex to the
+   * <code>searchResults</code>, unless it is a subregion of the last match
+   * recorded. <code>columnIndex</code> is advanced to the position after the
+   * start of the matched region, ready for the next search. Answers true if a
+   * match was added, else false.
+   * 
+   * @param seq
+   * @param searchPattern
+   * @param firstResiduePosition
+   * @return
+   */
+  protected boolean recordMatch(SequenceI seq, Regex searchPattern,
+          int firstResiduePosition)
+  {
+    /*
+     * get start/end of the match in sequence coordinates
+     */
+    int offset = searchPattern.matchedFrom();
+    int matchStartPosition = firstResiduePosition + offset;
+    int matchEndPosition = matchStartPosition
+            + searchPattern.charsMatched() - 1;
+
+    /*
+     * update columnIndex to next column after the start of the match
+     * (findIndex returns a value base 1, columnIndex is held base 0)
+     */
+    columnIndex = seq.findIndex(matchStartPosition);
+
+    /*
+     * check that this match is not a subset of the previous one (JAL-2302)
+     */
+    List<SearchResultMatchI> matches = searchResults.getResults();
+    SearchResultMatchI lastMatch = matches.isEmpty() ? null
+            : matches.get(matches.size() - 1);
+
+    if (lastMatch == null || !lastMatch.contains(seq, matchStartPosition,
+            matchEndPosition))
+    {
+      searchResults.addResult(seq, matchStartPosition, matchEndPosition);
+      return true;
     }
-    return found;
+
+    return false;
   }
 
   /**
@@ -254,26 +354,30 @@ public class Finder
    * <li>match search string to sequence id</li>
    * <li>match search string to sequence description (optional)</li>
    * </ul>
-   * Answers true if a match is found and we are not doing 'find all' (so this
-   * search action is complete), else false.
+   * Answers true if a match is found, else false.
    * 
    * @param seq
    * @param searchString
-   * @param regex
+   * @param searchPattern
    * @return
    */
   protected boolean doNonMotifSearches(SequenceI seq, String searchString,
-          Regex regex)
+          Regex searchPattern)
   {
-    if (searchForResidueNumber(seq, searchString) && !findAll)
+    /*
+     * position sequence search to start of sequence
+     */
+    columnIndex = 0;
+
+    if (searchForResidueNumber(seq, searchString))
     {
       return true;
     }
-    if (searchSequenceName(seq, regex) && !findAll)
+    if (searchSequenceName(seq, searchPattern))
     {
       return true;
     }
-    if (searchSequenceDescription(seq, regex) && !findAll)
+    if (includeDescription && searchSequenceDescription(seq, searchPattern))
     {
       return true;
     }
@@ -281,22 +385,18 @@ public class Finder
   }
 
   /**
-   * Searches for a match with the sequence description, if that option was
-   * requested, and if found, adds the sequence to the list of match ids (but
-   * not as a duplicate). Answers true if a match was added, else false.
+   * Searches for a match with the sequence description, and if found, adds the
+   * sequence to the list of match ids (but not as a duplicate). Answers true if
+   * a match was added, else false.
    * 
    * @param seq
-   * @param regex
+   * @param searchPattern
    * @return
    */
-  protected boolean searchSequenceDescription(SequenceI seq, Regex regex)
+  protected boolean searchSequenceDescription(SequenceI seq, Regex searchPattern)
   {
-    if (!includeDescription)
-    {
-      return false;
-    }
     String desc = seq.getDescription();
-    if (desc != null && regex.search(desc) && !idMatch.contains(seq))
+    if (desc != null && searchPattern.search(desc) && !idMatch.contains(seq))
     {
       idMatch.addElement(seq);
       return true;
@@ -310,12 +410,12 @@ public class Finder
    * a match was added, else false.
    * 
    * @param seq
-   * @param regex
+   * @param searchPattern
    * @return
    */
-  protected boolean searchSequenceName(SequenceI seq, Regex regex)
+  protected boolean searchSequenceName(SequenceI seq, Regex searchPattern)
   {
-    if (regex.search(seq.getName()) && !idMatch.contains(seq))
+    if (searchPattern.search(seq.getName()) && !idMatch.contains(seq))
     {
       idMatch.addElement(seq);
       return true;
@@ -325,7 +425,8 @@ public class Finder
 
   /**
    * Tries to interpret the search string as a residue position, and if valid,
-   * adds the position to the search results
+   * adds the position to the search results and returns true, else answers
+   * false
    */
   protected boolean searchForResidueNumber(SequenceI seq, String searchString)
   {
@@ -365,8 +466,8 @@ public class Finder
   }
 
   /**
-   * Returns the (possibly empty) list of matching sequences (when search
-   * includes searching sequence names)
+   * Returns the (possibly empty) list of sequences matched on sequence name or
+   * description
    * 
    * @return
    */
@@ -376,7 +477,9 @@ public class Finder
   }
 
   /**
-   * @return the searchResults
+   * Answers the search results (possibly empty) from the last search
+   * 
+   * @return
    */
   public SearchResultsI getSearchResults()
   {
@@ -384,19 +487,26 @@ public class Finder
   }
 
   /**
-   * @return the resIndex
+   * Answers the absolute column position (base 0, including any hidden columns)
+   * of the start of the last sequence motif (residue pattern) match found. A
+   * 'Find next' will search from the next position.
+   * 
+   * @return
    */
-  public int getResIndex()
+  public int getColumnIndex()
   {
-    return resIndex;
+    return columnIndex;
   }
 
   /**
-   * @return the seqIndex
+   * Answers the offset in the alignment (0..) of the sequence in which the last
+   * match was found (if any)
+   * 
+   * @return
    */
-  public int getSeqIndex()
+  public int getSequenceIndex()
   {
-    return seqIndex;
+    return sequenceIndex;
   }
 
   /**
index f44387f..286cd5f 100644 (file)
@@ -68,21 +68,21 @@ public class Finder extends Panel implements ActionListener
   private SearchResultsI searchResults;
 
   /*
-   * sequence index and residue position of last match,
+   * sequence index and column position of last match,
    * for current search and per viewport
    */
   private int seqIndex = 0;
 
-  private int resIndex = -1;
+  private int colIndex = -1;
 
   Map<AlignViewportI, Integer> seqIndices;
 
-  Map<AlignViewportI, Integer> resIndices;
+  Map<AlignViewportI, Integer> colIndices;
 
   public Finder(final AlignmentPanel ap)
   {
     seqIndices = new HashMap<>();
-    resIndices = new HashMap<>();
+    colIndices = new HashMap<>();
 
     try
     {
@@ -126,7 +126,7 @@ public class Finder extends Panel implements ActionListener
 
     else if (evt.getSource() == findAll)
     {
-      resIndex = -1;
+      colIndex = -1;
       seqIndex = 0;
       doSearch(true);
     }
@@ -165,19 +165,19 @@ public class Finder extends Panel implements ActionListener
       ap = ap.av.applet.currentAlignFrame.alignPanel;
       av = ap.av;
       seqIndex = 0;
-      resIndex = -1;
+      colIndex = -1;
       if (seqIndices.containsKey(av))
       {
         seqIndex = seqIndices.get(av).intValue();
       }
-      if (resIndices.containsKey(av))
+      if (colIndices.containsKey(av))
       {
-        resIndex = resIndices.get(av).intValue();
+        colIndex = colIndices.get(av).intValue();
       }
     }
     createFeatures.setEnabled(false);
     jalview.analysis.Finder finder = new jalview.analysis.Finder(
-            av.getAlignment(), av.getSelectionGroup(), seqIndex, resIndex);
+            av.getAlignment(), av.getSelectionGroup(), seqIndex, colIndex);
     finder.setCaseSensitive(caseSensitive.getState());
     finder.setIncludeDescription(searchDescription.getState());
     finder.setFindAll(doFindAll);
@@ -186,10 +186,10 @@ public class Finder extends Panel implements ActionListener
 
     finder.find(searchString);
 
-    seqIndex = finder.getSeqIndex();
-    resIndex = finder.getResIndex();
+    seqIndex = finder.getSequenceIndex();
+    colIndex = finder.getColumnIndex();
     seqIndices.put(av, seqIndex);
-    resIndices.put(av, resIndex);
+    colIndices.put(av, colIndex);
     searchResults = finder.getSearchResults();
 
     Vector<SequenceI> idMatch = finder.getIdMatch();
@@ -213,7 +213,7 @@ public class Finder extends Panel implements ActionListener
     {
       ap.alignFrame.statusBar.setText(
               MessageManager.getString("label.finished_searching"));
-      resIndex = -1;
+      colIndex = -1;
       seqIndex = 0;
     }
     else
index 15e269c..af1c47b 100755 (executable)
@@ -395,7 +395,7 @@ public class IdPanel extends Panel
   {
     idCanvas.setHighlighted(list);
 
-    if (list == null)
+    if (list == null || list.isEmpty())
     {
       return;
     }
index 1cbe6ab..c5b25bf 100755 (executable)
@@ -45,6 +45,7 @@ import javax.swing.JComponent;
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 import javax.swing.KeyStroke;
+import javax.swing.event.InternalFrameAdapter;
 import javax.swing.event.InternalFrameEvent;
 
 /**
@@ -80,21 +81,26 @@ public class Finder extends GFinder
    */
   private int seqIndex = 0;
 
-  private int resIndex = -1;
+  private int colIndex = -1;
 
-  Map<AlignViewportI, Integer> seqIndices;
+  private Map<AlignViewportI, Integer> seqIndices;
 
-  Map<AlignViewportI, Integer> resIndices;
+  private Map<AlignViewportI, Integer> colIndices;
 
   private SearchResultsI searchResults;
 
+  /*
+   * true if we only search a given alignment view
+   */
+  private boolean focusfixed;
+
   /**
-   * Creates a new Finder object with no associated viewport or panel.
+   * Creates a new Finder object with no associated viewport or panel. Each Find
+   * or Find Next action will act on whichever viewport has focus at the time.
    */
   public Finder()
   {
     this(null, null);
-    focusfixed = false;
   }
 
   /**
@@ -109,13 +115,13 @@ public class Finder extends GFinder
     av = viewport;
     ap = alignPanel;
     seqIndices = new HashMap<>();
-    resIndices = new HashMap<>();
-    focusfixed = true;
+    colIndices = new HashMap<>();
+    focusfixed = viewport != null;
     frame = new JInternalFrame();
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
     frame.addInternalFrameListener(
-            new javax.swing.event.InternalFrameAdapter()
+            new InternalFrameAdapter()
             {
               @Override
               public void internalFrameClosing(InternalFrameEvent e)
@@ -148,12 +154,10 @@ public class Finder extends GFinder
   }
 
   /**
-   * Performs the 'Find Next' action.
-   * 
-   * @param e
+   * Performs the 'Find Next' action on the alignment panel with focus
    */
   @Override
-  public void findNext_actionPerformed(ActionEvent e)
+  public void findNext_actionPerformed()
   {
     if (getFocusedViewport())
     {
@@ -162,27 +166,20 @@ public class Finder extends GFinder
   }
 
   /**
-   * Performs the 'Find All' action.
-   * 
-   * @param e
+   * Performs the 'Find All' action on the alignment panel with focus
    */
   @Override
-  public void findAll_actionPerformed(ActionEvent e)
+  public void findAll_actionPerformed()
   {
     if (getFocusedViewport())
     {
-      resIndex = -1;
+      colIndex = -1;
       seqIndex = 0;
       doSearch(true);
     }
   }
 
   /**
-   * do we only search a given alignment view ?
-   */
-  private boolean focusfixed;
-
-  /**
    * if !focusfixed and not in a desktop environment, checks that av and ap are
    * valid. Otherwise, gets the topmost alignment window and sets av and ap
    * accordingly
@@ -217,14 +214,14 @@ public class Finder extends GFinder
          * panel where we have previously searched
          */
         seqIndex = 0;
-        resIndex = -1;
+        colIndex = -1;
         if (seqIndices.containsKey(av))
         {
           seqIndex = seqIndices.get(av).intValue();
         }
-        if (resIndices.containsKey(av))
+        if (colIndices.containsKey(av))
         {
-          resIndex = resIndices.get(av).intValue();
+          colIndex = colIndices.get(av).intValue();
         }
         return true;
       }
@@ -291,7 +288,7 @@ public class Finder extends GFinder
     // TODO: add switches to control what is searched - sequences, IDS,
     // descriptions, features
     jalview.analysis.Finder finder = new jalview.analysis.Finder(
-            av.getAlignment(), av.getSelectionGroup(), seqIndex, resIndex);
+            av.getAlignment(), av.getSelectionGroup(), seqIndex, colIndex);
     finder.setCaseSensitive(caseSensitive.isSelected());
     finder.setIncludeDescription(searchDescription.isSelected());
 
@@ -299,10 +296,10 @@ public class Finder extends GFinder
 
     finder.find(searchString);
 
-    seqIndex = finder.getSeqIndex();
-    resIndex = finder.getResIndex();
+    seqIndex = finder.getSequenceIndex();
+    colIndex = finder.getColumnIndex();
     seqIndices.put(av, seqIndex);
-    resIndices.put(av, resIndex);
+    colIndices.put(av, colIndex);
 
     searchResults = finder.getSearchResults();
     Vector<SequenceI> idMatch = finder.getIdMatch();
@@ -325,7 +322,7 @@ public class Finder extends GFinder
       JvOptionPane.showInternalMessageDialog(this,
               MessageManager.getString("label.finished_searching"), null,
               JvOptionPane.INFORMATION_MESSAGE);
-      resIndex = -1;
+      colIndex = -1;
       seqIndex = 0;
     }
     else
@@ -346,7 +343,7 @@ public class Finder extends GFinder
         }
         JvOptionPane.showInternalMessageDialog(this, message, null,
                 JvOptionPane.INFORMATION_MESSAGE);
-        resIndex = -1;
+        colIndex = -1;
         seqIndex = 0;
       }
     }
index 1ea4ab5..b433570 100755 (executable)
@@ -47,41 +47,20 @@ import javax.swing.text.JTextComponent;
 
 public class GFinder extends JPanel
 {
-  JLabel jLabelFind = new JLabel();
+  private static final java.awt.Font VERDANA_12 = new java.awt.Font("Verdana", 0,
+          12);
 
-  protected JButton findAll = new JButton();
-
-  protected JButton findNext = new JButton();
-
-  JPanel actionsPanel = new JPanel();
-
-  GridLayout gridLayout1 = new GridLayout();
+  private static final String FINDER_CACHE_KEY = "CACHE.FINDER";
 
   protected JButton createFeatures = new JButton();
 
-  protected JvCacheableInputBox<String> searchBox = new JvCacheableInputBox<String>(
+  protected JvCacheableInputBox<String> searchBox = new JvCacheableInputBox<>(
           getCacheKey());
 
-  BorderLayout mainBorderLayout = new BorderLayout();
-
-  JPanel jPanel2 = new JPanel();
-
-  JPanel jPanel3 = new JPanel();
-
-  JPanel jPanel4 = new JPanel();
-
-  BorderLayout borderLayout2 = new BorderLayout();
-
-  JPanel jPanel6 = new JPanel();
-
   protected JCheckBox caseSensitive = new JCheckBox();
 
   protected JCheckBox searchDescription = new JCheckBox();
 
-  GridLayout optionsGridLayout = new GridLayout();
-
-  private static final String FINDER_CACHE_KEY = "CACHE.FINDER";
-
   public GFinder()
   {
     try
@@ -95,35 +74,47 @@ public class GFinder extends JPanel
 
   private void jbInit() throws Exception
   {
-    jLabelFind.setFont(new java.awt.Font("Verdana", 0, 12));
-    jLabelFind.setText(MessageManager.getString("label.find"));
+    BorderLayout mainBorderLayout = new BorderLayout();
     this.setLayout(mainBorderLayout);
-    findAll.setFont(new java.awt.Font("Verdana", 0, 12));
-    findAll.setText(MessageManager.getString("action.find_all"));
+    mainBorderLayout.setHgap(5);
+    mainBorderLayout.setVgap(5);
+
+    JLabel jLabelFind = new JLabel(MessageManager.getString("label.find"));
+    jLabelFind.setFont(VERDANA_12);
+
+    JButton findAll = new JButton(
+            MessageManager.getString("action.find_all"));
+    findAll.setFont(VERDANA_12);
     findAll.addActionListener(new java.awt.event.ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        findAll_actionPerformed(e);
+        findAll_actionPerformed();
       }
     });
-    findNext.setFont(new java.awt.Font("Verdana", 0, 12));
-    findNext.setText(MessageManager.getString("action.find_next"));
+
+    JButton findNext = new JButton(
+            MessageManager.getString("action.find_next"));
+    findNext.setFont(VERDANA_12);
     findNext.addActionListener(new java.awt.event.ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        findNext_actionPerformed(e);
+        findNext_actionPerformed();
       }
     });
+
+    JPanel actionsPanel = new JPanel();
+    GridLayout gridLayout1 = new GridLayout();
     actionsPanel.setLayout(gridLayout1);
     gridLayout1.setHgap(0);
     gridLayout1.setRows(3);
     gridLayout1.setVgap(2);
+
     createFeatures.setEnabled(false);
-    createFeatures.setFont(new java.awt.Font("Verdana", 0, 12));
+    createFeatures.setFont(VERDANA_12);
     createFeatures.setMargin(new Insets(0, 0, 0, 0));
     createFeatures.setText(MessageManager.getString("label.new_feature"));
     createFeatures.addActionListener(new java.awt.event.ActionListener()
@@ -141,7 +132,7 @@ public class GFinder extends JPanel
               @Override
               public void caretUpdate(CaretEvent e)
               {
-                textfield_caretUpdate(e);
+                textfield_caretUpdate();
               }
             });
     searchBox.getEditor().getEditorComponent()
@@ -153,11 +144,7 @@ public class GFinder extends JPanel
                 textfield_keyPressed(e);
               }
             });
-    mainBorderLayout.setHgap(5);
-    mainBorderLayout.setVgap(5);
-    jPanel4.setLayout(borderLayout2);
-    jPanel2.setPreferredSize(new Dimension(10, 1));
-    jPanel3.setPreferredSize(new Dimension(10, 1));
+
     caseSensitive.setHorizontalAlignment(SwingConstants.LEFT);
     caseSensitive.setText(MessageManager.getString("label.match_case"));
 
@@ -169,6 +156,13 @@ public class GFinder extends JPanel
     actionsPanel.add(createFeatures, null);
     this.add(jLabelFind, java.awt.BorderLayout.WEST);
     this.add(actionsPanel, java.awt.BorderLayout.EAST);
+
+    JPanel jPanel2 = new JPanel();
+    jPanel2.setPreferredSize(new Dimension(10, 1));
+    JPanel jPanel3 = new JPanel();
+    jPanel3.setPreferredSize(new Dimension(10, 1));
+    JPanel jPanel4 = new JPanel();
+    jPanel4.setLayout(new BorderLayout());
     this.add(jPanel2, java.awt.BorderLayout.SOUTH);
     this.add(jPanel3, java.awt.BorderLayout.NORTH);
     this.add(jPanel4, java.awt.BorderLayout.CENTER);
@@ -176,6 +170,7 @@ public class GFinder extends JPanel
 
     JPanel optionsPanel = new JPanel();
 
+    GridLayout optionsGridLayout = new GridLayout();
     optionsGridLayout.setHgap(0);
     optionsGridLayout.setRows(2);
     optionsGridLayout.setVgap(2);
@@ -193,16 +188,16 @@ public class GFinder extends JPanel
       if (!searchBox.isPopupVisible())
       {
         e.consume();
-        findNext_actionPerformed(null);
+        findNext_actionPerformed();
       }
     }
   }
 
-  protected void findNext_actionPerformed(ActionEvent e)
+  protected void findNext_actionPerformed()
   {
   }
 
-  protected void findAll_actionPerformed(ActionEvent e)
+  protected void findAll_actionPerformed()
   {
   }
 
@@ -210,9 +205,10 @@ public class GFinder extends JPanel
   {
   }
 
-  public void textfield_caretUpdate(CaretEvent e)
+  public void textfield_caretUpdate()
   {
-    if (searchBox.getUserInput().indexOf(">") > -1)
+    // disabled as appears to be running a non-functional
+    if (false && searchBox.getUserInput().indexOf(">") > -1)
     {
       SwingUtilities.invokeLater(new Runnable()
       {
@@ -233,7 +229,7 @@ public class GFinder extends JPanel
             str = jalview.analysis.AlignSeq.extractGaps(
                     jalview.util.Comparison.GapChars,
                     al.getSequenceAt(0).getSequenceAsString());
-
+            // todo and what? set str as searchBox text?
           }
         }
       });
index 837e970..bd827f9 100644 (file)
@@ -52,6 +52,7 @@ public class AlignSeqTest
     assertEquals(AlignSeq.extractGaps(" -", " AC-G.T"), "ACG.T");
     assertEquals(AlignSeq.extractGaps(" -.", " AC-G.T ."), "ACGT");
     assertEquals(AlignSeq.extractGaps("-", " AC-G.T"), " ACG.T");
+    assertEquals(AlignSeq.extractGaps("-. ", " -. .-"), "");
   }
 
   @Test(groups = { "Functional" })
index 771712d..e959e62 100644 (file)
@@ -27,6 +27,8 @@ import static org.testng.Assert.assertTrue;
 import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.Sequence;
@@ -61,7 +63,7 @@ public class FinderTest
     Cache.applicationProperties.setProperty("PAD_GAPS",
             Boolean.FALSE.toString());
 
-    String seqData = "seq1seq1/8-16 ABCD--EF-GHI\n" + "seq2 A--BCDefHI\n"
+    String seqData = "seq1seq1/8-18 ABCD--EF-GHIJI\n" + "seq2 A--BCDefHI\n"
             + "seq3 --bcdEFH\n" + "seq4 aa---aMMMMMaaa\n";
     af = new FileLoader().LoadFileWaitTillLoaded(seqData,
             DataSourceType.PASTE);
@@ -83,7 +85,7 @@ public class FinderTest
     SearchResultsI sr = f.getSearchResults();
     assertEquals(sr.getSize(), 1);
     List<SearchResultMatchI> matches = sr.getResults();
-    assertSame(al.getSequenceAt(1), matches.get(0).getSequence());
+    assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
     assertEquals(matches.get(0).getStart(), 5);
     assertEquals(matches.get(0).getEnd(), 7);
 
@@ -94,8 +96,8 @@ public class FinderTest
     sr = f.getSearchResults();
     assertEquals(sr.getSize(), 2);
     matches = sr.getResults();
-    assertSame(al.getSequenceAt(1), matches.get(0).getSequence());
-    assertSame(al.getSequenceAt(2), matches.get(1).getSequence());
+    assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
+    assertSame(matches.get(1).getSequence(), al.getSequenceAt(2));
     assertEquals(matches.get(0).getStart(), 5);
     assertEquals(matches.get(0).getEnd(), 7);
     assertEquals(matches.get(1).getStart(), 4);
@@ -117,7 +119,7 @@ public class FinderTest
     SearchResultsI sr = f.getSearchResults();
     assertEquals(sr.getSize(), 1);
     List<SearchResultMatchI> matches = sr.getResults();
-    assertSame(al.getSequenceAt(0), matches.get(0).getSequence());
+    assertSame(matches.get(0).getSequence(), al.getSequenceAt(0));
     assertEquals(matches.get(0).getStart(), 9);
     assertEquals(matches.get(0).getEnd(), 9);
 
@@ -130,8 +132,8 @@ public class FinderTest
     sr = f.getSearchResults();
     assertEquals(sr.getSize(), 2);
     matches = sr.getResults();
-    assertSame(al.getSequenceAt(0), matches.get(0).getSequence());
-    assertSame(al.getSequenceAt(3), matches.get(1).getSequence());
+    assertSame(matches.get(0).getSequence(), al.getSequenceAt(0));
+    assertSame(matches.get(1).getSequence(), al.getSequenceAt(3));
     assertEquals(matches.get(0).getStart(), 9);
     assertEquals(matches.get(0).getEnd(), 9);
     assertEquals(matches.get(1).getStart(), 9);
@@ -152,7 +154,7 @@ public class FinderTest
   public void testFindNext()
   {
     /*
-     * start at second sequence; resIndex of -1
+     * start at second sequence; colIndex of -1
      * means sequence id / description is searched
      */
     Finder f = new Finder(al, null, 1, -1);
@@ -162,8 +164,9 @@ public class FinderTest
     assertEquals(f.getIdMatch().size(), 1);
     assertSame(f.getIdMatch().get(0), al.getSequenceAt(1));
 
-    // resIndex is now 0 - for use in next find next
-    assertEquals(f.getResIndex(), 0);
+    // colIndex is now 0 - for use in next find next
+    // searching A--BCDefHI
+    assertEquals(f.getColumnIndex(), 0);
     f = new Finder(al, null, 1, 0);
     f.find("e"); // matches in sequence
     assertTrue(f.getIdMatch().isEmpty());
@@ -173,16 +176,16 @@ public class FinderTest
     assertEquals(matches.get(0).getEnd(), 5);
     assertSame(matches.get(0).getSequence(), al.getSequenceAt(1));
     // still in the second sequence
-    assertEquals(f.getSeqIndex(), 1);
-    // next residue position to search from is 5
-    // (used as base 0 by RegEx so the same as 6 if base 1)
-    assertEquals(f.getResIndex(), 5);
+    assertEquals(f.getSequenceIndex(), 1);
+    // next column position to search from is 7
+    assertEquals(f.getColumnIndex(), 7);
 
     // find next from end of sequence - finds next sequence id
-    f = new Finder(al, null, 1, 5);
+    f = new Finder(al, null, 1, 7);
     f.find("e");
     assertEquals(f.getIdMatch().size(), 1);
     assertSame(f.getIdMatch().get(0), al.getSequenceAt(2));
+    assertTrue(f.getSearchResults().isEmpty());
   }
 
   /**
@@ -250,13 +253,13 @@ public class FinderTest
 
     assertEquals(f.getSearchResults().getSize(), 2);
     SearchResultMatchI match = f.getSearchResults().getResults().get(0);
-    assertSame(al2.getSequenceAt(1), match.getSequence());
-    assertEquals(5, match.getStart());
-    assertEquals(7, match.getEnd());
+    assertSame(match.getSequence(), al2.getSequenceAt(1));
+    assertEquals(match.getStart(), 5);
+    assertEquals(match.getEnd(), 7);
     match = f.getSearchResults().getResults().get(1);
-    assertSame(al2.getSequenceAt(2), match.getSequence());
-    assertEquals(4, match.getStart());
-    assertEquals(6, match.getEnd());
+    assertSame(match.getSequence(), al2.getSequenceAt(2));
+    assertEquals(match.getStart(), 4);
+    assertEquals(match.getEnd(), 6);
   }
 
   /**
@@ -275,7 +278,8 @@ public class FinderTest
     f.find("SEQ1");
     assertEquals(f.getIdMatch().size(), 1);
     assertSame(f.getIdMatch().get(0), al.getSequenceAt(0));
-    assertTrue(f.getSearchResults().isEmpty());
+    SearchResultsI searchResults = f.getSearchResults();
+    assertTrue(searchResults.isEmpty());
 
     /*
      * case sensitive
@@ -284,7 +288,8 @@ public class FinderTest
     f.setFindAll(true);
     f.setCaseSensitive(true);
     f.find("SEQ1");
-    assertTrue(f.getSearchResults().isEmpty());
+    searchResults = f.getSearchResults();
+    assertTrue(searchResults.isEmpty());
 
     /*
      * match both sequence id and sequence
@@ -296,31 +301,66 @@ public class FinderTest
     f.find("ABZ");
     assertEquals(f.getIdMatch().size(), 1);
     assertSame(f.getIdMatch().get(0), al2.getSequenceAt(4));
-    assertEquals(f.getSearchResults().getSize(), 2);
-    SearchResultMatchI match = f.getSearchResults().getResults().get(0);
-    assertSame(al2.getSequenceAt(4), match.getSequence());
-    assertEquals(4, match.getStart());
-    assertEquals(6, match.getEnd());
-    match = f.getSearchResults().getResults().get(1);
-    assertSame(al2.getSequenceAt(4), match.getSequence());
-    assertEquals(10, match.getStart());
-    assertEquals(12, match.getEnd());
+    searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 2);
+    SearchResultMatchI match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al2.getSequenceAt(4));
+    assertEquals(match.getStart(), 4);
+    assertEquals(match.getEnd(), 6);
+    match = searchResults.getResults().get(1);
+    assertSame(match.getSequence(), al2.getSequenceAt(4));
+    assertEquals(match.getStart(), 10);
+    assertEquals(match.getEnd(), 12);
   }
 
   /**
    * Test finding next match of a sequence pattern in an alignment
    */
   @Test(groups = "Functional")
-  public void testFind()
+  public void testFind_findNext()
   {
+    /*
+     * efh should be matched in seq2 only
+     */
     Finder f = new Finder(al, null);
     f.find("EfH");
     SearchResultsI searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 1);
     SearchResultMatchI match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(1), match.getSequence());
-    assertEquals(5, match.getStart());
-    assertEquals(7, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(1));
+    assertEquals(match.getStart(), 5);
+    assertEquals(match.getEnd(), 7);
+
+    /*
+     * I should be found in seq1 (twice) and seq2 (once)
+     */
+    f = new Finder(al, null);
+    f.find("I"); // find next: seq1/16
+    searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 1);
+    match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al.getSequenceAt(0));
+    assertEquals(match.getStart(), 16);
+    assertEquals(match.getEnd(), 16);
+
+    f.find("I"); // find next: seq1/18
+    searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 1);
+    match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al.getSequenceAt(0));
+    assertEquals(match.getStart(), 18);
+    assertEquals(match.getEnd(), 18);
+
+    f.find("I"); // find next: seq2/8
+    searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 1);
+    match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al.getSequenceAt(1));
+    assertEquals(match.getStart(), 8);
+    assertEquals(match.getEnd(), 8);
+
+    f.find("I");
+    assertTrue(f.getSearchResults().isEmpty());
   }
 
   /**
@@ -336,9 +376,9 @@ public class FinderTest
     SearchResultsI searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 1);
     SearchResultMatchI match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(3), match.getSequence());
-    assertEquals(4, match.getStart()); // dataset sequence positions
-    assertEquals(8, match.getEnd()); // base 1
+    assertSame(match.getSequence(), al.getSequenceAt(3));
+    assertEquals(match.getStart(), 4); // dataset sequence positions
+    assertEquals(match.getEnd(), 8); // base 1
   }
 
   /**
@@ -353,13 +393,32 @@ public class FinderTest
     SearchResultsI searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 2);
     SearchResultMatchI match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(1), match.getSequence());
-    assertEquals(5, match.getStart());
-    assertEquals(7, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(1));
+    assertEquals(match.getStart(), 5);
+    assertEquals(match.getEnd(), 7);
+    match = searchResults.getResults().get(1);
+    assertSame(match.getSequence(), al.getSequenceAt(2));
+    assertEquals(match.getStart(), 4);
+    assertEquals(match.getEnd(), 6);
+
+    /*
+     * find all I should find 2 positions in seq1, 1 in seq2
+     */
+    f.find("I");
+    searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 3);
+    match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al.getSequenceAt(0));
+    assertEquals(match.getStart(), 16);
+    assertEquals(match.getEnd(), 16);
     match = searchResults.getResults().get(1);
-    assertSame(al.getSequenceAt(2), match.getSequence());
-    assertEquals(4, match.getStart());
-    assertEquals(6, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(0));
+    assertEquals(match.getStart(), 18);
+    assertEquals(match.getEnd(), 18);
+    match = searchResults.getResults().get(2);
+    assertSame(match.getSequence(), al.getSequenceAt(1));
+    assertEquals(match.getStart(), 8);
+    assertEquals(match.getEnd(), 8);
   }
 
   /**
@@ -371,17 +430,38 @@ public class FinderTest
     Finder f = new Finder(al, null);
     f.setCaseSensitive(true);
     f.setFindAll(true);
+
+    /*
+     * BC should match seq1/9-10 and seq2/2-3
+     */
     f.find("BC");
     SearchResultsI searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 2);
     SearchResultMatchI match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(0), match.getSequence());
+    assertSame(match.getSequence(), al.getSequenceAt(0));
     assertEquals(match.getStart(), 9);
     assertEquals(match.getEnd(), 10);
     match = searchResults.getResults().get(1);
-    assertSame(al.getSequenceAt(1), match.getSequence());
+    assertSame(match.getSequence(), al.getSequenceAt(1));
     assertEquals(match.getStart(), 2);
     assertEquals(match.getEnd(), 3);
+
+    /*
+     * bc should match seq3/1-2
+     */
+    f = new Finder(al, null);
+    f.setCaseSensitive(true);
+    f.setFindAll(true);
+    f.find("bc");
+    searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 1);
+    match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al.getSequenceAt(2));
+    assertEquals(match.getStart(), 1);
+    assertEquals(match.getEnd(), 2);
+
+    f.find("bC");
+    assertTrue(f.getSearchResults().isEmpty());
   }
 
   /**
@@ -407,9 +487,9 @@ public class FinderTest
     SearchResultsI searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 1);
     SearchResultMatchI match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(1), match.getSequence());
-    assertEquals(2, match.getStart());
-    assertEquals(2, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(1));
+    assertEquals(match.getStart(), 2);
+    assertEquals(match.getEnd(), 2);
 
     /*
      * a second Find should not return the 'b' in seq3 as outside the selection
@@ -424,17 +504,17 @@ public class FinderTest
     searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 1);
     match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(1), match.getSequence());
-    assertEquals(4, match.getStart());
-    assertEquals(4, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(1));
+    assertEquals(match.getStart(), 4);
+    assertEquals(match.getEnd(), 4);
     f.find("d");
     assertTrue(f.getIdMatch().isEmpty());
     searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 1);
     match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(2), match.getSequence());
-    assertEquals(3, match.getStart());
-    assertEquals(3, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(2));
+    assertEquals(match.getStart(), 3);
+    assertEquals(match.getEnd(), 3);
   }
 
   /**
@@ -466,9 +546,9 @@ public class FinderTest
     SearchResultsI searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 1);
     SearchResultMatchI match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(2), match.getSequence());
-    assertEquals(4, match.getStart());
-    assertEquals(4, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(2));
+    assertEquals(match.getStart(), 4);
+    assertEquals(match.getEnd(), 4);
 
     /*
      * search for 'Q' should match two sequence ids only
@@ -511,12 +591,83 @@ public class FinderTest
     SearchResultsI searchResults = f.getSearchResults();
     assertEquals(searchResults.getSize(), 2);
     SearchResultMatchI match = searchResults.getResults().get(0);
-    assertSame(al.getSequenceAt(0), match.getSequence());
-    assertEquals(16, match.getStart());
-    assertEquals(16, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(0));
+    assertEquals(match.getStart(), 16);
+    assertEquals(match.getEnd(), 16);
     match = searchResults.getResults().get(1);
-    assertSame(al.getSequenceAt(1), match.getSequence());
-    assertEquals(8, match.getStart());
-    assertEquals(8, match.getEnd());
+    assertSame(match.getSequence(), al.getSequenceAt(1));
+    assertEquals(match.getStart(), 8);
+    assertEquals(match.getEnd(), 8);
+  }
+
+  /**
+   * Test that find does not report hidden positions
+   */
+  @Test(groups = "Functional")
+  public void testFind_withHiddenColumns()
+  {
+    /*
+     * 0    5   9
+     * ABCD--EF-GHI
+     * A--BCDefHI
+     * --bcdEFH
+     * aa---aMMMMMaaa
+     */
+
+    /*
+     * hide 2-4 (CD- -BC bcd ---)
+     */
+    HiddenColumns hc = new HiddenColumns();
+    hc.hideColumns(2, 4);
+    al.setHiddenColumns(hc);
+
+    /*
+     * find all search for D should ignore hidden positions in seq1 and seq3,
+     * find the visible D in seq2
+     */
+    Finder f = new Finder(al, null);
+    f.setFindAll(true);
+    f.find("D");
+    SearchResultsI searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 1);
+    SearchResultMatchI match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al.getSequenceAt(1));
+    assertEquals(match.getStart(), 4);
+    assertEquals(match.getEnd(), 4);
+
+    /*
+     * hide columns 2-5:
+     * find all 'aaa' should find end of seq4 only
+     */
+    hc.hideColumns(2, 5);
+    f = new Finder(al, null);
+    f.find("aaa");
+    searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 1);
+    match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al.getSequenceAt(3));
+    assertEquals(match.getStart(), 9);
+    assertEquals(match.getEnd(), 11);
+
+    /*
+     * find all 'BE' should not match across hidden columns in seq1
+     */
+    f.find("BE");
+    assertTrue(f.getSearchResults().isEmpty());
+
+    /*
+     * boundary case: hide columns at end of alignment
+     * search for H should match seq3/6 only
+     */
+    hc.revealAllHiddenColumns(new ColumnSelection());
+    hc.hideColumns(8, 13);
+    f = new Finder(al, null);
+    f.find("H");
+    searchResults = f.getSearchResults();
+    assertEquals(searchResults.getSize(), 1);
+    match = searchResults.getResults().get(0);
+    assertSame(match.getSequence(), al.getSequenceAt(2));
+    assertEquals(match.getStart(), 6);
+    assertEquals(match.getEnd(), 6);
   }
 }