+ * If there are hidden columns, and option {@ignoreHidden} is selected, then + * only visible positions of the sequence are included, and a mapping is also + * constructed from the returned string positions to the true sequence + * positions. + *
+ * Note we have to do this each time {@code findNext} or {@code findAll} is + * called, in case the alignment, selection group or hidden columns have + * changed. In particular, if the sequence at offset {@code sequenceIndex} in + * the alignment is (no longer) in the selection group, search is advanced to + * the next sequence that is. + *
+ * Sets sequence string to the empty string if there are no more sequences (in + * selection group if any) at or after {@code sequenceIndex}. + *
+ * Returns true if a sequence could be found, false if end of alignment was + * reached + * + * @param ignoreHidden + * @return + */ + private boolean getSequence(boolean ignoreHidden) + { + AlignmentI alignment = viewport.getAlignment(); + if (sequenceIndex >= alignment.getHeight()) + { + seqToSearch = ""; + return false; + } + SequenceI seq = alignment.getSequenceAt(sequenceIndex); + SequenceGroup selection = viewport.getSelectionGroup(); + if (selection != null && !selection.contains(seq)) + { + if (!nextSequence(ignoreHidden)) + { + return false; + } + seq = alignment.getSequenceAt(sequenceIndex); + } + + String seqString = null; + if (ignoreHidden) + { + seqString = getVisibleSequence(seq); + this.searchedSequenceStartPosition = 1; + } + else + { + int startCol = 0; + int endCol = seq.getLength() - 1; + this.searchedSequenceStartPosition = seq.getStart(); + if (selection != null) + { + startCol = selection.getStartRes(); + endCol = Math.min(endCol, selection.getEndRes()); + this.searchedSequenceStartPosition = seq.findPosition(startCol); + } + seqString = seq.getSequenceAsString(startCol, endCol + 1); + } + + /* + * remove gaps; note that even if this leaves an empty string, we 'search' + * the sequence anyway (for possible match on name or description) + */ + String ungapped = AlignSeq.extractGaps(Comparison.GapChars, seqString); + this.seqToSearch = ungapped; + + return true; + } + + /** + * Returns a string consisting of only the visible residues of {@code seq} from + * alignment column {@ fromColumn}, restricted to the current selection region + * if there is one. + *
+ * As a side-effect, also computes the mapping from the true sequence positions
+ * to the positions (1, 2, ...) of the returned sequence. This is to allow
+ * search matches in the visible sequence to be converted to sequence positions.
+ *
+ * @param seq
+ * @return
+ */
+ private String getVisibleSequence(SequenceI seq)
+ {
+ /*
+ * get start / end columns of sequence and convert to base 0
+ * (so as to match the visible column ranges)
+ */
+ int seqStartCol = seq.findIndex(seq.getStart()) - 1;
+ int seqEndCol = seq.findIndex(seq.getStart() + seq.getLength() - 1) - 1;
+ Iterator
+ * Returns true if a sequence could be found, false if end of alignment was
+ * reached
+ *
+ * @param ignoreHidden
+ */
+ private boolean nextSequence(boolean ignoreHidden)
+ {
+ sequenceIndex++;
+ residueIndex = -1;
+
+ return getSequence(ignoreHidden);
+ }
+
+ /**
+ * Finds the next match in the given sequence, starting at offset
+ * {@code residueIndex}. Answers true if a match is found, else false.
+ *
+ * If a match is found, {@code residueIndex} is advanced to the position after
+ * the start of the matched region, ready for the next search.
+ *
+ * If no match is found, {@code sequenceIndex} is advanced ready to search the
+ * next sequence.
+ *
+ * @param seqToSearch
+ * @param searchString
+ * @param searchPattern
+ * @param matchDescription
+ * @param ignoreHidden
+ * @return
+ */
+ protected boolean findNextMatch(String searchString,
+ Regex searchPattern, boolean matchDescription,
+ boolean ignoreHidden)
+ {
+ if (residueIndex < 0)
+ {
+ /*
+ * at start of sequence; try find by residue number, in sequence id,
+ * or (optionally) in sequence description
+ */
+ if (doNonMotifSearches(searchString, searchPattern,
+ matchDescription))
+ {
+ return true;
+ }
+ }
+
+ /*
+ * search for next match in sequence string
+ */
+ int end = seqToSearch.length();
+ while (residueIndex < end)
+ {
+ boolean matched = searchPattern.searchFrom(seqToSearch, residueIndex);
+ if (matched)
+ {
+ if (recordMatch(searchPattern, ignoreHidden))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ residueIndex = Integer.MAX_VALUE;
+ }
+ }
+
+ nextSequence(ignoreHidden);
+ return false;
+ }
+
+ /**
+ * Adds the match held in the
+ * Matches that lie entirely within hidden regions of the alignment are not
+ * added.
+ *
+ * @param searchPattern
+ * @param ignoreHidden
+ * @return
+ */
+ protected boolean recordMatch(Regex searchPattern, boolean ignoreHidden)
+ {
+ SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
+
+ /*
+ * convert start/end of the match to sequence coordinates
+ */
+ int offset = searchPattern.matchedFrom();
+ int matchStartPosition = this.searchedSequenceStartPosition + offset;
+ int matchEndPosition = matchStartPosition
+ + searchPattern.charsMatched() - 1;
+
+ /*
+ * update residueIndex to next position after the start of the match
+ * (findIndex returns a value base 1, columnIndex is held base 0)
+ */
+ residueIndex += offset + 1;
+
+ /*
+ * return false if the match is entirely in a hidden region
+ */
+ if (allHidden(seq, matchStartPosition, matchEndPosition))
+ {
+ return false;
+ }
+
+ /*
+ * check that this match is not a subset of the previous one (JAL-2302)
+ */
+ ListsearchPattern
Regex to the
+ * searchResults
, unless it is a subregion of the last match
+ * recorded. residueIndex
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.
+ *
+ *
+ * Answers true if a match is found, else false.
+ *
+ * @param searchString
+ * @param searchPattern
+ * @param includeDescription
+ * @return
+ */
+ protected boolean doNonMotifSearches(String searchString,
+ Regex searchPattern, boolean includeDescription)
+ {
+ SequenceI seq = viewport.getAlignment().getSequenceAt(sequenceIndex);
+
+ /*
+ * position sequence search to start of sequence
+ */
+ residueIndex = 0;
+ try
+ {
+ int res = Integer.parseInt(searchString);
+ return searchForResidueNumber(seq, res);
+ } catch (NumberFormatException ex)
+ {
+ // search pattern is not a number
+ }
+
+ if (searchSequenceName(seq, searchPattern))
+ {
+ return true;
+ }
+ if (includeDescription && searchSequenceDescription(seq, searchPattern))
+ {
+ return true;
+ }
+ return 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 searchPattern
+ * @return
+ */
+ protected boolean searchSequenceDescription(SequenceI seq, Regex searchPattern)
+ {
+ String desc = seq.getDescription();
+ if (desc != null && searchPattern.search(desc) && !idMatches.contains(seq))
+ {
+ idMatches.add(seq);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Searches for a match with the sequence name, 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 searchPattern
+ * @return
+ */
+ protected boolean searchSequenceName(SequenceI seq, Regex searchPattern)
+ {
+ if (searchPattern.search(seq.getName()) && !idMatches.contains(seq))
+ {
+ idMatches.add(seq);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * If the residue position is valid for the sequence, and in a visible column,
+ * adds the position to the search results and returns true, else answers false.
+ *
+ * @param seq
+ * @param resNo
+ * @return
+ */
+ protected boolean searchForResidueNumber(SequenceI seq, int resNo)
+ {
+ if (seq.getStart() <= resNo && seq.getEnd() >= resNo)
+ {
+ if (isVisible(seq, resNo))
+ {
+ searchResults.addResult(seq, resNo, resNo);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the residue is in a visible column, else false
+ *
+ * @param seq
+ * @param res
+ * @return
+ */
+ private boolean isVisible(SequenceI seq, int res)
+ {
+ if (!viewport.hasHiddenColumns())
+ {
+ return true;
+ }
+ int col = seq.findIndex(res); // base 1
+ return viewport.getAlignment().getHiddenColumns().isVisible(col - 1); // base 0
+ }
+
+ @Override
+ public List