2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.datamodel;
23 import jalview.util.ShiftList;
24 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
25 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Vector;
33 * NOTE: Columns are zero based.
35 public class ColumnSelection
37 Vector<Integer> selected = new Vector<Integer>();
39 // Vector of int [] {startCol, endCol}
40 Vector<int[]> hiddenColumns;
43 * Add a column to the selection
48 public void addElement(int col)
50 Integer column = new Integer(col);
51 if (!selected.contains(column))
53 selected.addElement(column);
58 * clears column selection
62 selected.removeAllElements();
66 * Removes value 'col' from the selection (not the col'th item)
69 * index of column to be removed
71 public void removeElement(int col)
73 Integer colInt = new Integer(col);
75 if (selected.contains(colInt))
77 // if this ever changes to List.remove(), ensure Integer not int argument
78 // as List.remove(int i) removes the i'th item which is wrong
79 selected.removeElement(colInt);
84 * removes a range of columns from the selection
87 * int - first column in range to be removed
91 public void removeElements(int start, int end)
94 for (int i = start; i < end; i++)
96 colInt = new Integer(i);
97 if (selected.contains(colInt))
99 selected.removeElement(colInt);
106 * @return Vector containing selected columns as Integers
108 public Vector<Integer> getSelected()
116 * index to search for in column selection
118 * @return true if Integer(col) is in selection.
120 public boolean contains(int col)
122 return selected.contains(new Integer(col));
126 * Column number at position i in selection
129 * index into selected columns
131 * @return column number in alignment
133 public int columnAt(int i)
135 return selected.elementAt(i).intValue();
141 * @return DOCUMENT ME!
145 return selected.size();
149 * rightmost selected column
151 * @return rightmost column in alignment that is selected
157 for (int i = 0; i < selected.size(); i++)
159 if (columnAt(i) > max)
169 * Leftmost column in selection
171 * @return column index of leftmost column in selection
175 int min = 1000000000;
177 for (int i = 0; i < selected.size(); i++)
179 if (columnAt(i) < min)
189 * propagate shift in alignment columns to column selection
194 * shift in edit (+ve for removal, or -ve for inserts)
196 public List<int[]> compensateForEdit(int start, int change)
198 List<int[]> deletedHiddenColumns = null;
199 for (int i = 0; i < size(); i++)
201 int temp = columnAt(i);
205 // if this ever changes to List.set(), swap parameter order!!
206 selected.setElementAt(new Integer(temp - change), i);
210 if (hiddenColumns != null)
212 deletedHiddenColumns = new ArrayList<int[]>();
213 int hSize = hiddenColumns.size();
214 for (int i = 0; i < hSize; i++)
216 int[] region = hiddenColumns.elementAt(i);
217 if (region[0] > start && start + change > region[1])
219 deletedHiddenColumns.add(region);
221 hiddenColumns.removeElementAt(i);
227 if (region[0] > start)
240 this.revealHiddenColumns(0);
243 return deletedHiddenColumns;
247 * propagate shift in alignment columns to column selection special version of
248 * compensateForEdit - allowing for edits within hidden regions
253 * shift in edit (+ve for removal, or -ve for inserts)
255 private void compensateForDelEdits(int start, int change)
257 for (int i = 0; i < size(); i++)
259 int temp = columnAt(i);
263 // if this ever changes to List.set(), swap parameter order!!
264 selected.setElementAt(new Integer(temp - change), i);
268 if (hiddenColumns != null)
270 for (int i = 0; i < hiddenColumns.size(); i++)
272 int[] region = hiddenColumns.elementAt(i);
273 if (region[0] >= start)
277 if (region[1] >= start)
281 if (region[1] < region[0])
283 hiddenColumns.removeElementAt(i--);
299 * Adjust hidden column boundaries based on a series of column additions or
300 * deletions in visible regions.
305 public ShiftList compensateForEdits(ShiftList shiftrecord)
307 if (shiftrecord != null)
309 final List<int[]> shifts = shiftrecord.getShifts();
310 if (shifts != null && shifts.size() > 0)
313 for (int i = 0, j = shifts.size(); i < j; i++)
315 int[] sh = shifts.get(i);
316 // compensateForEdit(shifted+sh[0], sh[1]);
317 compensateForDelEdits(shifted + sh[0], sh[1]);
321 return shiftrecord.getInverse();
327 * removes intersection of position,length ranges in deletions from the
328 * start,end regions marked in intervals.
334 private boolean pruneIntervalVector(final List<int[]> shifts,
335 Vector<int[]> intervals)
337 boolean pruned = false;
338 int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
339 int hr[] = intervals.elementAt(i);
340 int sr[] = shifts.get(s);
341 while (i <= j && s <= t)
343 boolean trailinghn = hr[1] >= sr[0];
348 hr = intervals.elementAt(++i);
356 int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
357 if (endshift < hr[0] || endshift < sr[0])
358 { // leadinghc disjoint or not a deletion
361 sr = shifts.get(++s);
369 boolean leadinghn = hr[0] >= sr[0];
370 boolean leadinghc = hr[0] < endshift;
371 boolean trailinghc = hr[1] < endshift;
375 { // deleted hidden region.
376 intervals.removeElementAt(i);
381 hr = intervals.elementAt(i);
387 hr[0] = endshift; // clip c terminal region
388 leadinghn = !leadinghn;
404 // sr contained in hr
407 sr = shifts.get(++s);
417 return pruned; // true if any interval was removed or modified by
421 private boolean pruneColumnList(final List<int[]> shifts,
422 Vector<Integer> list)
424 int s = 0, t = shifts.size();
425 int[] sr = shifts.get(s++);
426 boolean pruned = false;
427 int i = 0, j = list.size();
428 while (i < j && s <= t)
430 int c = list.elementAt(i++).intValue();
433 if (sr[1] + sr[0] >= c)
434 { // sr[1] -ve means inseriton.
435 list.removeElementAt(--i);
452 * remove any hiddenColumns or selected columns and shift remaining based on a
453 * series of position, range deletions.
457 public void pruneDeletions(ShiftList deletions)
459 if (deletions != null)
461 final List<int[]> shifts = deletions.getShifts();
462 if (shifts != null && shifts.size() > 0)
464 // delete any intervals intersecting.
465 if (hiddenColumns != null)
467 pruneIntervalVector(shifts, hiddenColumns);
468 if (hiddenColumns != null && hiddenColumns.size() == 0)
470 hiddenColumns = null;
473 if (selected != null && selected.size() > 0)
475 pruneColumnList(shifts, selected);
476 if (selected != null && selected.size() == 0)
481 // and shift the rest.
482 this.compensateForEdits(deletions);
488 * This Method is used to return all the HiddenColumn regions
490 * @return empty list or List of hidden column intervals
492 public List<int[]> getHiddenColumns()
494 return hiddenColumns == null ? Collections.<int[]> emptyList()
499 * Return absolute column index for a visible column index
502 * int column index in alignment view
503 * @return alignment column index for column
505 public int adjustForHiddenColumns(int column)
508 if (hiddenColumns != null)
510 for (int i = 0; i < hiddenColumns.size(); i++)
512 int[] region = hiddenColumns.elementAt(i);
513 if (result >= region[0])
515 result += region[1] - region[0] + 1;
523 * Use this method to find out where a column will appear in the visible
524 * alignment when hidden columns exist. If the column is not visible, then the
525 * left-most visible column will always be returned.
527 * @param hiddenColumn
531 public int findColumnPosition(int hiddenColumn)
533 int result = hiddenColumn;
534 if (hiddenColumns != null)
540 region = hiddenColumns.elementAt(index++);
541 if (hiddenColumn > region[1])
543 result -= region[1] + 1 - region[0];
545 } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
546 if (hiddenColumn > region[0] && hiddenColumn < region[1])
548 return region[0] + hiddenColumn - result;
551 return result; // return the shifted position after removing hidden columns.
555 * Use this method to determine where the next hiddenRegion starts
557 public int findHiddenRegionPosition(int hiddenRegion)
560 if (hiddenColumns != null)
566 int[] region = hiddenColumns.elementAt(index);
567 if (hiddenRegion == 0)
572 gaps += region[1] + 1 - region[0];
573 result = region[1] + 1;
575 } while (index < hiddenRegion + 1);
584 * THis method returns the rightmost limit of a region of an alignment with
585 * hidden columns. In otherwords, the next hidden column.
590 public int getHiddenBoundaryRight(int alPos)
592 if (hiddenColumns != null)
597 int[] region = hiddenColumns.elementAt(index);
598 if (alPos < region[0])
604 } while (index < hiddenColumns.size());
612 * This method returns the leftmost limit of a region of an alignment with
613 * hidden columns. In otherwords, the previous hidden column.
618 public int getHiddenBoundaryLeft(int alPos)
620 if (hiddenColumns != null)
622 int index = hiddenColumns.size() - 1;
625 int[] region = hiddenColumns.elementAt(index);
626 if (alPos > region[1])
632 } while (index > -1);
639 public void hideSelectedColumns()
643 int column = getSelected().firstElement().intValue();
649 public void hideColumns(int start, int end)
651 if (hiddenColumns == null)
653 hiddenColumns = new Vector<int[]>();
656 boolean added = false;
657 boolean overlap = false;
659 for (int i = 0; i < hiddenColumns.size(); i++)
661 int[] region = hiddenColumns.elementAt(i);
662 if (start <= region[1] && end >= region[0])
664 hiddenColumns.removeElementAt(i);
668 else if (end < region[0] && start < region[0])
670 hiddenColumns.insertElementAt(new int[] { start, end }, i);
678 hideColumns(start, end);
682 hiddenColumns.addElement(new int[] { start, end });
688 * This method will find a range of selected columns around the column
694 public void hideColumns(int col)
696 // First find out range of columns to hide
697 int min = col, max = col + 1;
698 while (contains(min))
704 while (contains(max))
717 hideColumns(min, max);
720 public void revealAllHiddenColumns()
722 if (hiddenColumns != null)
724 for (int i = 0; i < hiddenColumns.size(); i++)
726 int[] region = hiddenColumns.elementAt(i);
727 for (int j = region[0]; j < region[1] + 1; j++)
734 hiddenColumns = null;
737 public void revealHiddenColumns(int res)
739 for (int i = 0; i < hiddenColumns.size(); i++)
741 int[] region = hiddenColumns.elementAt(i);
742 if (res == region[0])
744 for (int j = region[0]; j < region[1] + 1; j++)
749 hiddenColumns.removeElement(region);
753 if (hiddenColumns.size() == 0)
755 hiddenColumns = null;
759 public boolean isVisible(int column)
761 if (hiddenColumns != null)
763 for (int i = 0; i < hiddenColumns.size(); i++)
765 int[] region = hiddenColumns.elementAt(i);
766 if (column >= region[0] && column <= region[1])
781 public ColumnSelection(ColumnSelection copy)
785 if (copy.selected != null)
787 selected = new Vector<Integer>();
788 for (int i = 0, j = copy.selected.size(); i < j; i++)
790 selected.addElement(copy.selected.elementAt(i));
793 if (copy.hiddenColumns != null)
795 hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
796 for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
799 rh = copy.hiddenColumns.elementAt(i);
802 cp = new int[rh.length];
803 System.arraycopy(rh, 0, cp, 0, rh.length);
804 hiddenColumns.addElement(cp);
814 public ColumnSelection()
818 public String[] getVisibleSequenceStrings(int start, int end,
821 int i, iSize = seqs.length;
822 String selection[] = new String[iSize];
823 if (hiddenColumns != null && hiddenColumns.size() > 0)
825 for (i = 0; i < iSize; i++)
827 StringBuffer visibleSeq = new StringBuffer();
828 List<int[]> regions = getHiddenColumns();
830 int blockStart = start, blockEnd = end;
832 int hideStart, hideEnd;
834 for (int j = 0; j < regions.size(); j++)
836 region = regions.get(j);
837 hideStart = region[0];
840 if (hideStart < start)
845 blockStart = Math.min(blockStart, hideEnd + 1);
846 blockEnd = Math.min(blockEnd, hideStart);
848 if (blockStart > blockEnd)
853 visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
855 blockStart = hideEnd + 1;
859 if (end > blockStart)
861 visibleSeq.append(seqs[i].getSequence(blockStart, end));
864 selection[i] = visibleSeq.toString();
869 for (i = 0; i < iSize; i++)
871 selection[i] = seqs[i].getSequenceAsString(start, end);
879 * return all visible segments between the given start and end boundaries
882 * (first column inclusive from 0)
884 * (last column - not inclusive)
885 * @return int[] {i_start, i_end, ..} where intervals lie in
886 * start<=i_start<=i_end<end
888 public int[] getVisibleContigs(int start, int end)
890 if (hiddenColumns != null && hiddenColumns.size() > 0)
892 List<int[]> visiblecontigs = new ArrayList<int[]>();
893 List<int[]> regions = getHiddenColumns();
897 int hideStart, hideEnd;
899 for (int j = 0; vstart < end && j < regions.size(); j++)
901 region = regions.get(j);
902 hideStart = region[0];
905 if (hideEnd < vstart)
909 if (hideStart > vstart)
911 visiblecontigs.add(new int[] { vstart, hideStart - 1 });
913 vstart = hideEnd + 1;
918 visiblecontigs.add(new int[] { vstart, end - 1 });
920 int[] vcontigs = new int[visiblecontigs.size() * 2];
921 for (int i = 0, j = visiblecontigs.size(); i < j; i++)
923 int[] vc = visiblecontigs.get(i);
924 visiblecontigs.set(i, null);
925 vcontigs[i * 2] = vc[0];
926 vcontigs[i * 2 + 1] = vc[1];
928 visiblecontigs.clear();
933 return new int[] { start, end - 1 };
938 * delete any columns in alignmentAnnotation that are hidden (including
939 * sequence associated annotation).
941 * @param alignmentAnnotation
943 public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
945 makeVisibleAnnotation(-1, -1, alignmentAnnotation);
949 * delete any columns in alignmentAnnotation that are hidden (including
950 * sequence associated annotation).
953 * remove any annotation to the right of this column
955 * remove any annotation to the left of this column
956 * @param alignmentAnnotation
957 * the annotation to operate on
959 public void makeVisibleAnnotation(int start, int end,
960 AlignmentAnnotation alignmentAnnotation)
962 if (alignmentAnnotation.annotations == null)
966 if (start == end && end == -1)
969 end = alignmentAnnotation.annotations.length;
971 if (hiddenColumns != null && hiddenColumns.size() > 0)
973 // then mangle the alignmentAnnotation annotation array
974 Vector<Annotation[]> annels = new Vector<Annotation[]>();
975 Annotation[] els = null;
976 List<int[]> regions = getHiddenColumns();
977 int blockStart = start, blockEnd = end;
979 int hideStart, hideEnd, w = 0;
981 for (int j = 0; j < regions.size(); j++)
983 region = regions.get(j);
984 hideStart = region[0];
987 if (hideStart < start)
992 blockStart = Math.min(blockStart, hideEnd + 1);
993 blockEnd = Math.min(blockEnd, hideStart);
995 if (blockStart > blockEnd)
1000 annels.addElement(els = new Annotation[blockEnd - blockStart]);
1001 System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1004 blockStart = hideEnd + 1;
1008 if (end > blockStart)
1010 annels.addElement(els = new Annotation[end - blockStart + 1]);
1011 if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
1013 // copy just the visible segment of the annotation row
1014 System.arraycopy(alignmentAnnotation.annotations, blockStart,
1015 els, 0, els.length);
1019 // copy to the end of the annotation row
1020 System.arraycopy(alignmentAnnotation.annotations, blockStart,
1022 (alignmentAnnotation.annotations.length - blockStart));
1031 alignmentAnnotation.annotations = new Annotation[w];
1034 for (Annotation[] chnk : annels)
1036 System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1043 alignmentAnnotation.restrict(start, end);
1048 * Invert the column selection from first to end-1. leaves hiddenColumns
1049 * untouched (and unselected)
1054 public void invertColumnSelection(int first, int width)
1056 boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
1057 for (int i = first; i < width; i++)
1065 if (!hasHidden || isVisible(i))
1074 * add in any unselected columns from the given column selection, excluding
1075 * any that are hidden.
1079 public void addElementsFrom(ColumnSelection colsel)
1081 if (colsel != null && colsel.size() > 0)
1083 for (Integer col : colsel.getSelected())
1085 if (hiddenColumns != null && isVisible(col.intValue()))
1087 if (!selected.contains(col))
1089 selected.addElement(col);
1097 * set the selected columns the given column selection, excluding any columns
1102 public void setElementsFrom(ColumnSelection colsel)
1104 selected = new Vector<Integer>();
1105 if (colsel.selected != null && colsel.selected.size() > 0)
1107 if (hiddenColumns != null && hiddenColumns.size() > 0)
1109 // only select visible columns in this columns selection
1110 addElementsFrom(colsel);
1114 // add everything regardless
1115 for (Integer col : colsel.getSelected())
1124 * Add gaps into the sequences aligned to profileseq under the given
1129 * - alignment to have gaps inserted into it
1131 * - alignment view where sequence corresponding to profileseq is
1133 * @return new Column selection for new alignment view, with insertions into
1134 * profileseq marked as hidden.
1136 public static ColumnSelection propagateInsertions(SequenceI profileseq,
1137 AlignmentI al, AlignmentView input)
1141 // return propagateInsertions(profileseq, al, )
1142 char gc = al.getGapCharacter();
1143 Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
1144 ColumnSelection nview = (ColumnSelection) alandcolsel[1];
1145 SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
1146 nview.propagateInsertions(profileseq, al, origseq);
1153 * - sequence in al which corresponds to origseq
1155 * - alignment which is to have gaps inserted into it
1157 * - sequence corresponding to profileseq which defines gap map for
1160 public void propagateInsertions(SequenceI profileseq, AlignmentI al,
1163 char gc = al.getGapCharacter();
1164 // recover mapping between sequence's non-gap positions and positions
1166 pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1167 int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
1170 // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
1171 // alandcolsel[0])[0].gapMap()))
1172 // add profile to visible contigs
1173 for (int v = 0; v < viscontigs.length; v += 2)
1175 if (viscontigs[v] > spos)
1177 StringBuffer sb = new StringBuffer();
1178 for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1182 for (int s = 0, ns = al.getHeight(); s < ns; s++)
1184 SequenceI sqobj = al.getSequenceAt(s);
1185 if (sqobj != profileseq)
1187 String sq = al.getSequenceAt(s).getSequenceAsString();
1188 if (sq.length() <= spos + offset)
1191 int diff = spos + offset - sq.length() - 1;
1196 while ((diff = spos + offset - sq.length() - 1) > 0)
1199 // + ((diff >= sb.length()) ? sb.toString() : sb
1200 // .substring(0, diff));
1201 if (diff >= sb.length())
1203 sq += sb.toString();
1207 char[] buf = new char[diff];
1208 sb.getChars(0, diff, buf, 0);
1209 sq += buf.toString();
1213 sq += sb.toString();
1217 al.getSequenceAt(s).setSequence(
1218 sq.substring(0, spos + offset) + sb.toString()
1219 + sq.substring(spos + offset));
1223 // offset+=sb.length();
1225 spos = viscontigs[v + 1] + 1;
1227 if ((offset + spos) < profileseq.getLength())
1229 // pad the final region with gaps.
1230 StringBuffer sb = new StringBuffer();
1231 for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1235 for (int s = 0, ns = al.getHeight(); s < ns; s++)
1237 SequenceI sqobj = al.getSequenceAt(s);
1238 if (sqobj == profileseq)
1242 String sq = sqobj.getSequenceAsString();
1244 int diff = origseq.getLength() - sq.length();
1248 // + ((diff >= sb.length()) ? sb.toString() : sb
1249 // .substring(0, diff));
1250 if (diff >= sb.length())
1252 sq += sb.toString();
1256 char[] buf = new char[diff];
1257 sb.getChars(0, diff, buf, 0);
1258 sq += buf.toString();
1260 diff = origseq.getLength() - sq.length();
1268 * @return true if there are columns marked
1270 public boolean hasSelectedColumns()
1272 return (selected != null && selected.size() > 0);
1277 * @return true if there are columns hidden
1279 public boolean hasHiddenColumns()
1281 return hiddenColumns != null && hiddenColumns.size() > 0;
1286 * @return true if there are more than one set of columns hidden
1288 public boolean hasManyHiddenColumns()
1290 return hiddenColumns != null && hiddenColumns.size() > 1;
1294 * mark the columns corresponding to gap characters as hidden in the column
1299 public void hideInsertionsFor(SequenceI sr)
1301 List<int[]> inserts = sr.getInsertions();
1302 for (int[] r : inserts)
1304 hideColumns(r[0], r[1]);
1308 public boolean filterAnnotations(Annotation[] annotations,
1309 AnnotationFilterParameter filterParams)
1311 this.revealAllHiddenColumns();
1316 if (annotations[count] != null)
1319 boolean itemMatched = false;
1321 if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
1322 && annotations[count].value >= filterParams
1323 .getThresholdValue())
1327 if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
1328 && annotations[count].value <= filterParams
1329 .getThresholdValue())
1334 if (filterParams.isFilterAlphaHelix()
1335 && annotations[count].secondaryStructure == 'H')
1340 if (filterParams.isFilterBetaSheet()
1341 && annotations[count].secondaryStructure == 'E')
1346 if (filterParams.isFilterTurn()
1347 && annotations[count].secondaryStructure == 'S')
1352 String regexSearchString = filterParams.getRegexString();
1353 if (regexSearchString != null
1354 && !filterParams.getRegexSearchFields().isEmpty())
1356 List<SearchableAnnotationField> fields = filterParams
1357 .getRegexSearchFields();
1360 if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
1361 && annotations[count].displayCharacter
1362 .matches(regexSearchString))
1366 } catch (java.util.regex.PatternSyntaxException pse)
1368 if (annotations[count].displayCharacter
1369 .equals(regexSearchString))
1374 if (fields.contains(SearchableAnnotationField.DESCRIPTION)
1375 && annotations[count].description != null
1376 && annotations[count].description
1377 .matches(regexSearchString))
1385 this.addElement(count);
1389 } while (count < annotations.length);