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
489 * @return empty list or List of hidden column intervals
491 public List<int[]> getHiddenColumns()
493 return hiddenColumns == null ? Collections.<int[]> emptyList()
498 * Return absolute column index for a visible column index
501 * int column index in alignment view
502 * @return alignment column index for column
504 public int adjustForHiddenColumns(int column)
507 if (hiddenColumns != null)
509 for (int i = 0; i < hiddenColumns.size(); i++)
511 int[] region = hiddenColumns.elementAt(i);
512 if (result >= region[0])
514 result += region[1] - region[0] + 1;
522 * Use this method to find out where a column will appear in the visible
523 * alignment when hidden columns exist. If the column is not visible, then the
524 * left-most visible column will always be returned.
526 * @param hiddenColumn
530 public int findColumnPosition(int hiddenColumn)
532 int result = hiddenColumn;
533 if (hiddenColumns != null)
539 region = hiddenColumns.elementAt(index++);
540 if (hiddenColumn > region[1])
542 result -= region[1] + 1 - region[0];
544 } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
545 if (hiddenColumn > region[0] && hiddenColumn < region[1])
547 return region[0] + hiddenColumn - result;
550 return result; // return the shifted position after removing hidden columns.
554 * Use this method to determine where the next hiddenRegion starts
556 public int findHiddenRegionPosition(int hiddenRegion)
559 if (hiddenColumns != null)
565 int[] region = hiddenColumns.elementAt(index);
566 if (hiddenRegion == 0)
571 gaps += region[1] + 1 - region[0];
572 result = region[1] + 1;
574 } while (index < hiddenRegion + 1);
583 * THis method returns the rightmost limit of a region of an alignment with
584 * hidden columns. In otherwords, the next hidden column.
589 public int getHiddenBoundaryRight(int alPos)
591 if (hiddenColumns != null)
596 int[] region = hiddenColumns.elementAt(index);
597 if (alPos < region[0])
603 } while (index < hiddenColumns.size());
611 * This method returns the leftmost limit of a region of an alignment with
612 * hidden columns. In otherwords, the previous hidden column.
617 public int getHiddenBoundaryLeft(int alPos)
619 if (hiddenColumns != null)
621 int index = hiddenColumns.size() - 1;
624 int[] region = hiddenColumns.elementAt(index);
625 if (alPos > region[1])
631 } while (index > -1);
638 public void hideSelectedColumns()
642 int column = getSelected().firstElement().intValue();
648 public void hideColumns(int start, int end)
650 if (hiddenColumns == null)
652 hiddenColumns = new Vector<int[]>();
655 boolean added = false;
656 boolean overlap = false;
658 for (int i = 0; i < hiddenColumns.size(); i++)
660 int[] region = hiddenColumns.elementAt(i);
661 if (start <= region[1] && end >= region[0])
663 hiddenColumns.removeElementAt(i);
667 else if (end < region[0] && start < region[0])
669 hiddenColumns.insertElementAt(new int[]
678 hideColumns(start, end);
682 hiddenColumns.addElement(new int[]
689 * This method will find a range of selected columns around the column
695 public void hideColumns(int col)
697 // First find out range of columns to hide
698 int min = col, max = col + 1;
699 while (contains(min))
705 while (contains(max))
718 hideColumns(min, max);
721 public void revealAllHiddenColumns()
723 if (hiddenColumns != null)
725 for (int i = 0; i < hiddenColumns.size(); i++)
727 int[] region = hiddenColumns.elementAt(i);
728 for (int j = region[0]; j < region[1] + 1; j++)
735 hiddenColumns = null;
738 public void revealHiddenColumns(int res)
740 for (int i = 0; i < hiddenColumns.size(); i++)
742 int[] region = hiddenColumns.elementAt(i);
743 if (res == region[0])
745 for (int j = region[0]; j < region[1] + 1; j++)
750 hiddenColumns.removeElement(region);
754 if (hiddenColumns.size() == 0)
756 hiddenColumns = null;
760 public boolean isVisible(int column)
762 if (hiddenColumns != null)
764 for (int i = 0; i < hiddenColumns.size(); i++)
766 int[] region = hiddenColumns.elementAt(i);
767 if (column >= region[0] && column <= region[1])
782 public ColumnSelection(ColumnSelection copy)
786 if (copy.selected != null)
788 selected = new Vector<Integer>();
789 for (int i = 0, j = copy.selected.size(); i < j; i++)
791 selected.addElement(copy.selected.elementAt(i));
794 if (copy.hiddenColumns != null)
796 hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
797 for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
800 rh = copy.hiddenColumns.elementAt(i);
803 cp = new int[rh.length];
804 System.arraycopy(rh, 0, cp, 0, rh.length);
805 hiddenColumns.addElement(cp);
815 public ColumnSelection()
819 public String[] getVisibleSequenceStrings(int start, int end,
822 int i, iSize = seqs.length;
823 String selection[] = new String[iSize];
824 if (hiddenColumns != null && hiddenColumns.size() > 0)
826 for (i = 0; i < iSize; i++)
828 StringBuffer visibleSeq = new StringBuffer();
829 List<int[]> regions = getHiddenColumns();
831 int blockStart = start, blockEnd = end;
833 int hideStart, hideEnd;
835 for (int j = 0; j < regions.size(); j++)
837 region = regions.get(j);
838 hideStart = region[0];
841 if (hideStart < start)
846 blockStart = Math.min(blockStart, hideEnd + 1);
847 blockEnd = Math.min(blockEnd, hideStart);
849 if (blockStart > blockEnd)
854 visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
856 blockStart = hideEnd + 1;
860 if (end > blockStart)
862 visibleSeq.append(seqs[i].getSequence(blockStart, end));
865 selection[i] = visibleSeq.toString();
870 for (i = 0; i < iSize; i++)
872 selection[i] = seqs[i].getSequenceAsString(start, end);
880 * return all visible segments between the given start and end boundaries
883 * (first column inclusive from 0)
885 * (last column - not inclusive)
886 * @return int[] {i_start, i_end, ..} where intervals lie in
887 * start<=i_start<=i_end<end
889 public int[] getVisibleContigs(int start, int end)
891 if (hiddenColumns != null && hiddenColumns.size() > 0)
893 List<int[]> visiblecontigs = new ArrayList<int[]>();
894 List<int[]> regions = getHiddenColumns();
898 int hideStart, hideEnd;
900 for (int j = 0; vstart < end && j < regions.size(); j++)
902 region = regions.get(j);
903 hideStart = region[0];
906 if (hideEnd < vstart)
910 if (hideStart > vstart)
912 visiblecontigs.add(new int[]
913 { vstart, hideStart - 1 });
915 vstart = hideEnd + 1;
920 visiblecontigs.add(new int[]
921 { vstart, end - 1 });
923 int[] vcontigs = new int[visiblecontigs.size() * 2];
924 for (int i = 0, j = visiblecontigs.size(); i < j; i++)
926 int[] vc = visiblecontigs.get(i);
927 visiblecontigs.set(i, null);
928 vcontigs[i * 2] = vc[0];
929 vcontigs[i * 2 + 1] = vc[1];
931 visiblecontigs.clear();
942 * delete any columns in alignmentAnnotation that are hidden (including
943 * sequence associated annotation).
945 * @param alignmentAnnotation
947 public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
949 makeVisibleAnnotation(-1, -1, alignmentAnnotation);
953 * delete any columns in alignmentAnnotation that are hidden (including
954 * sequence associated annotation).
957 * remove any annotation to the right of this column
959 * remove any annotation to the left of this column
960 * @param alignmentAnnotation
961 * the annotation to operate on
963 public void makeVisibleAnnotation(int start, int end,
964 AlignmentAnnotation alignmentAnnotation)
966 if (alignmentAnnotation.annotations == null)
970 if (start == end && end == -1)
973 end = alignmentAnnotation.annotations.length;
975 if (hiddenColumns != null && hiddenColumns.size() > 0)
977 // then mangle the alignmentAnnotation annotation array
978 Vector<Annotation []> annels = new Vector<Annotation []>();
979 Annotation[] els = null;
980 List<int[]> regions = getHiddenColumns();
981 int blockStart = start, blockEnd = end;
983 int hideStart, hideEnd, w = 0;
985 for (int j = 0; j < regions.size(); j++)
987 region = regions.get(j);
988 hideStart = region[0];
991 if (hideStart < start)
996 blockStart = Math.min(blockStart, hideEnd + 1);
997 blockEnd = Math.min(blockEnd, hideStart);
999 if (blockStart > blockEnd)
1004 annels.addElement(els = new Annotation[blockEnd - blockStart]);
1005 System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1008 blockStart = hideEnd + 1;
1012 if (end > blockStart)
1014 annels.addElement(els = new Annotation[end - blockStart + 1]);
1015 if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
1017 // copy just the visible segment of the annotation row
1018 System.arraycopy(alignmentAnnotation.annotations, blockStart,
1019 els, 0, els.length);
1023 // copy to the end of the annotation row
1024 System.arraycopy(alignmentAnnotation.annotations, blockStart,
1026 (alignmentAnnotation.annotations.length - blockStart));
1035 alignmentAnnotation.annotations = new Annotation[w];
1038 for (Annotation [] chnk : annels)
1040 System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1047 alignmentAnnotation.restrict(start, end);
1052 * Invert the column selection from first to end-1. leaves hiddenColumns
1053 * untouched (and unselected)
1058 public void invertColumnSelection(int first, int width)
1060 boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
1061 for (int i = first; i < width; i++)
1069 if (!hasHidden || isVisible(i))
1078 * add in any unselected columns from the given column selection, excluding
1079 * any that are hidden.
1083 public void addElementsFrom(ColumnSelection colsel)
1085 if (colsel != null && colsel.size() > 0)
1087 for (Integer col : colsel.getSelected())
1089 if (hiddenColumns != null && isVisible(col.intValue()))
1091 if (!selected.contains(col))
1093 selected.addElement(col);
1101 * set the selected columns the given column selection, excluding any columns
1106 public void setElementsFrom(ColumnSelection colsel)
1108 selected = new Vector<Integer>();
1109 if (colsel.selected != null && colsel.selected.size() > 0)
1111 if (hiddenColumns != null && hiddenColumns.size() > 0)
1113 // only select visible columns in this columns selection
1114 addElementsFrom(colsel);
1118 // add everything regardless
1119 for (Integer col : colsel.getSelected())
1128 * Add gaps into the sequences aligned to profileseq under the given
1133 * - alignment to have gaps inserted into it
1135 * - alignment view where sequence corresponding to profileseq is
1137 * @return new Column selection for new alignment view, with insertions into
1138 * profileseq marked as hidden.
1140 public static ColumnSelection propagateInsertions(SequenceI profileseq,
1141 AlignmentI al, AlignmentView input)
1145 // return propagateInsertions(profileseq, al, )
1146 char gc = al.getGapCharacter();
1147 Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
1148 ColumnSelection nview = (ColumnSelection) alandcolsel[1];
1149 SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
1150 nview.propagateInsertions(profileseq, al, origseq);
1157 * - sequence in al which corresponds to origseq
1159 * - alignment which is to have gaps inserted into it
1161 * - sequence corresponding to profileseq which defines gap map for
1164 public void propagateInsertions(SequenceI profileseq, AlignmentI al,
1167 char gc = al.getGapCharacter();
1168 // recover mapping between sequence's non-gap positions and positions
1170 pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1171 int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
1174 // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
1175 // alandcolsel[0])[0].gapMap()))
1176 // add profile to visible contigs
1177 for (int v = 0; v < viscontigs.length; v += 2)
1179 if (viscontigs[v] > spos)
1181 StringBuffer sb = new StringBuffer();
1182 for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1186 for (int s = 0, ns = al.getHeight(); s < ns; s++)
1188 SequenceI sqobj = al.getSequenceAt(s);
1189 if (sqobj != profileseq)
1191 String sq = al.getSequenceAt(s).getSequenceAsString();
1192 if (sq.length() <= spos + offset)
1195 int diff = spos + offset - sq.length() - 1;
1200 while ((diff = spos + offset - sq.length() - 1) > 0)
1203 // + ((diff >= sb.length()) ? sb.toString() : sb
1204 // .substring(0, diff));
1205 if (diff >= sb.length())
1207 sq += sb.toString();
1211 char[] buf = new char[diff];
1212 sb.getChars(0, diff, buf, 0);
1213 sq += buf.toString();
1217 sq += sb.toString();
1221 al.getSequenceAt(s).setSequence(
1222 sq.substring(0, spos + offset) + sb.toString()
1223 + sq.substring(spos + offset));
1227 // offset+=sb.length();
1229 spos = viscontigs[v + 1] + 1;
1231 if ((offset + spos) < profileseq.getLength())
1233 // pad the final region with gaps.
1234 StringBuffer sb = new StringBuffer();
1235 for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1239 for (int s = 0, ns = al.getHeight(); s < ns; s++)
1241 SequenceI sqobj = al.getSequenceAt(s);
1242 if (sqobj == profileseq)
1246 String sq = sqobj.getSequenceAsString();
1248 int diff = origseq.getLength() - sq.length();
1252 // + ((diff >= sb.length()) ? sb.toString() : sb
1253 // .substring(0, diff));
1254 if (diff >= sb.length())
1256 sq += sb.toString();
1260 char[] buf = new char[diff];
1261 sb.getChars(0, diff, buf, 0);
1262 sq += buf.toString();
1264 diff = origseq.getLength() - sq.length();
1272 * @return true if there are columns marked
1274 public boolean hasSelectedColumns()
1276 return (selected != null && selected.size() > 0);
1281 * @return true if there are columns hidden
1283 public boolean hasHiddenColumns()
1285 return hiddenColumns != null && hiddenColumns.size() > 0;
1290 * @return true if there are more than one set of columns hidden
1292 public boolean hasManyHiddenColumns()
1294 return hiddenColumns != null && hiddenColumns.size() > 1;
1298 * mark the columns corresponding to gap characters as hidden in the column
1303 public void hideInsertionsFor(SequenceI sr)
1305 List<int[]> inserts = sr.getInsertions();
1306 for (int[] r : inserts)
1308 hideColumns(r[0], r[1]);
1312 public boolean filterAnnotations(Annotation[] annotations,
1313 AnnotationFilterParameter filterParams)
1315 this.revealAllHiddenColumns();
1320 if (annotations[count] != null)
1323 boolean itemMatched = false;
1325 if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
1326 && annotations[count].value >= filterParams
1327 .getThresholdValue())
1331 if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
1332 && annotations[count].value <= filterParams
1333 .getThresholdValue())
1338 if (filterParams.isFilterAlphaHelix()
1339 && annotations[count].secondaryStructure == 'H')
1344 if (filterParams.isFilterBetaSheet()
1345 && annotations[count].secondaryStructure == 'E')
1350 if (filterParams.isFilterTurn()
1351 && annotations[count].secondaryStructure == 'S')
1356 String regexSearchString = filterParams.getRegexString();
1357 if (regexSearchString != null
1358 && !filterParams.getRegexSearchFields().isEmpty())
1360 List<SearchableAnnotationField> fields = filterParams
1361 .getRegexSearchFields();
1364 if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
1365 && annotations[count].displayCharacter
1366 .matches(regexSearchString))
1370 } catch (java.util.regex.PatternSyntaxException pse)
1372 if (annotations[count].displayCharacter
1373 .equals(regexSearchString))
1378 if (fields.contains(SearchableAnnotationField.DESCRIPTION)
1379 && annotations[count].description != null
1380 && annotations[count].description
1381 .matches(regexSearchString))
1389 this.addElement(count);
1393 } while (count < annotations.length);