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.BitSet;
29 import java.util.Collections;
30 import java.util.List;
33 * Data class holding the selected columns and hidden column ranges for a view.
36 public class ColumnSelection
39 * A class to hold an efficient representation of selected columns
44 * list of selected columns (ordered by selection order, not column order)
46 private List<Integer> order;
49 * an unmodifiable view of the selected columns list
51 private List<Integer> _uorder;
54 * bitfield for column selection - allows quick lookup
56 private BitSet selected;
63 order = new ArrayList<Integer>();
64 _uorder = Collections.unmodifiableList(order);
65 selected = new BitSet();
73 IntList(IntList other)
79 for (int i = 0; i < j; i++)
81 add(other.elementAt(i));
87 * adds a new column i to the selection - only if i is not already selected
95 order.add(Integer.valueOf(i));
109 Integer colInt = new Integer(col);
111 if (selected.get(col))
113 // if this ever changes to List.remove(), ensure Integer not int
115 // as List.remove(int i) removes the i'th item which is wrong
116 order.remove(colInt);
121 boolean contains(Integer colInt)
123 return selected.get(colInt);
128 return order.isEmpty();
132 * Returns a read-only view of the selected columns list
136 List<Integer> getList()
147 * gets the column that was selected first, second or i'th
157 protected boolean pruneColumnList(final List<int[]> shifts)
159 int s = 0, t = shifts.size();
160 int[] sr = shifts.get(s++);
161 boolean pruned = false;
162 int i = 0, j = order.size();
163 while (i < j && s <= t)
165 int c = order.get(i++).intValue();
168 if (sr[1] + sr[0] >= c)
169 { // sr[1] -ve means inseriton.
188 * shift every selected column at or above start by change
191 * - leftmost column to be shifted
195 void compensateForEdits(int start, int change)
197 BitSet mask = new BitSet();
198 for (int i = 0; i < order.size(); i++)
200 int temp = order.get(i);
204 // clear shifted bits and update List of selected columns
205 selected.clear(temp);
206 mask.set(temp - change);
207 order.set(i, new Integer(temp - change));
210 // lastly update the bitfield all at once
214 boolean isSelected(int column)
216 return selected.get(column);
221 return selected.length() - 1;
226 return selected.get(0) ? 0 : selected.nextSetBit(0);
230 * @return a series of selection intervals along the range
232 List<int[]> getRanges()
234 List<int[]> rlist = new ArrayList<int[]>();
235 if (selected.isEmpty())
239 int next = selected.nextSetBit(0), clear = -1;
242 clear = selected.nextClearBit(next);
243 rlist.add(new int[] { next, clear - 1 });
244 next = selected.nextSetBit(clear);
250 public int hashCode()
252 // TODO Auto-generated method stub
253 return selected.hashCode();
257 public boolean equals(Object obj)
259 if (obj instanceof IntList)
261 return ((IntList) obj).selected.equals(selected);
267 IntList selection = new IntList();
269 HiddenColumns hiddenColumns = new HiddenColumns();
272 * Add a column to the selection
277 public void addElement(int col)
283 * clears column selection
291 * Removes value 'col' from the selection (not the col'th item)
294 * index of column to be removed
296 public void removeElement(int col)
298 selection.remove(col);
302 * removes a range of columns from the selection
305 * int - first column in range to be removed
309 public void removeElements(int start, int end)
312 for (int i = start; i < end; i++)
314 colInt = new Integer(i);
315 if (selection.contains(colInt))
317 selection.remove(colInt);
323 * Returns a read-only view of the (possibly empty) list of selected columns
325 * The list contains no duplicates but is not necessarily ordered. It also may
326 * include columns hidden from the current view. To modify (for example sort)
327 * the list, you should first make a copy.
329 * The list is not thread-safe: iterating over it could result in
330 * ConcurrentModificationException if it is modified by another thread.
332 public List<Integer> getSelected()
334 return selection.getList();
338 * @return list of int arrays containing start and end column position for
339 * runs of selected columns ordered from right to left.
341 public List<int[]> getSelectedRanges()
343 return selection.getRanges();
349 * index to search for in column selection
351 * @return true if col is selected
353 public boolean contains(int col)
355 return (col > -1) ? selection.isSelected(col) : false;
359 * Answers true if no columns are selected, else false
361 public boolean isEmpty()
363 return selection == null || selection.isEmpty();
367 * rightmost selected column
369 * @return rightmost column in alignment that is selected
373 if (selection.isEmpty())
377 return selection.getMaxColumn();
381 * Leftmost column in selection
383 * @return column index of leftmost column in selection
387 if (selection.isEmpty())
391 return selection.getMinColumn();
395 * propagate shift in alignment columns to column selection
400 * shift in edit (+ve for removal, or -ve for inserts)
402 /* public List<int[]> compensateForEdit(int start, int change)
404 selection.compensateForEdits(start, change);
405 return hiddenColumns.compensateForEdit(start, change, this);
409 * propagate shift in alignment columns to column selection special version of
410 * compensateForEdit - allowing for edits within hidden regions
415 * shift in edit (+ve for removal, or -ve for inserts)
417 private void compensateForDelEdits(int start, int change)
419 selection.compensateForEdits(start, change);
420 hiddenColumns.compensateForDelEdits(start, change);
424 * Adjust hidden column boundaries based on a series of column additions or
425 * deletions in visible regions.
430 private ShiftList compensateForEdits(ShiftList shiftrecord)
432 if (shiftrecord != null)
434 final List<int[]> shifts = shiftrecord.getShifts();
435 if (shifts != null && shifts.size() > 0)
438 for (int i = 0, j = shifts.size(); i < j; i++)
440 int[] sh = shifts.get(i);
441 compensateForDelEdits(shifted + sh[0], sh[1]);
445 return shiftrecord.getInverse();
452 * remove any hiddenColumns or selected columns and shift remaining based on a
453 * series of position, range deletions.
457 private void pruneDeletions(ShiftList deletions)
459 if (deletions != null)
461 final List<int[]> shifts = deletions.getShifts();
462 if (shifts != null && shifts.size() > 0)
464 hiddenColumns.pruneDeletions(shifts);
466 if (selection != null && selection.size() > 0)
468 selection.pruneColumnList(shifts);
469 if (selection != null && selection.size() == 0)
474 // and shift the rest.
475 this.compensateForEdits(deletions);
481 public void hideSelectedColumns(AlignmentI al)
483 synchronized (selection)
485 for (int[] selregions : selection.getRanges())
487 al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
496 * Hides the specified column and any adjacent selected columns
501 public void hideSelectedColumns(int col, AlignmentI al)
504 * deselect column (whether selected or not!)
509 * find adjacent selected columns
511 int min = col - 1, max = col + 1;
512 while (contains(min))
518 while (contains(max))
525 * min, max are now the closest unselected columns
534 al.getHiddenColumns().hideColumns(min, max);
546 public ColumnSelection(ColumnSelection copy)
550 selection = new IntList(copy.selection);
557 public ColumnSelection()
567 * Invert the column selection from first to end-1. leaves hiddenColumns
568 * untouched (and unselected)
573 public void invertColumnSelection(int first, int width, AlignmentI al)
575 boolean hasHidden = al.getHiddenColumns().hasHidden();
576 for (int i = first; i < width; i++)
584 if (!hasHidden || al.getHiddenColumns().isVisible(i))
593 * add in any unselected columns from the given column selection, excluding
594 * any that are hidden.
598 public void addElementsFrom(ColumnSelection colsel)
600 if (colsel != null && !colsel.isEmpty())
602 for (Integer col : colsel.getSelected())
604 if (hiddenColumns != null
605 && hiddenColumns.isVisible(col.intValue()))
614 * set the selected columns the given column selection, excluding any columns
619 public void setElementsFrom(ColumnSelection colsel)
621 selection = new IntList();
622 if (colsel.selection != null && colsel.selection.size() > 0)
624 if (hiddenColumns.hasHidden())
626 // only select visible columns in this columns selection
627 addElementsFrom(colsel);
631 // add everything regardless
632 for (Integer col : colsel.getSelected())
641 * Add gaps into the sequences aligned to profileseq under the given
646 * - alignment to have gaps inserted into it
648 * - alignment view where sequence corresponding to profileseq is
650 * @return new Column selection for new alignment view, with insertions into
651 * profileseq marked as hidden.
653 public static ColumnSelection propagateInsertions(SequenceI profileseq,
654 AlignmentI al, AlignmentView input)
658 // return propagateInsertions(profileseq, al, )
659 char gc = al.getGapCharacter();
660 Object[] alandcolsel = input.getAlignmentAndHiddenColumns(gc);
661 ColumnSelection nview = (ColumnSelection) alandcolsel[1];
662 SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
663 nview.propagateInsertions(profileseq, al, origseq);
670 * - sequence in al which corresponds to origseq
672 * - alignment which is to have gaps inserted into it
674 * - sequence corresponding to profileseq which defines gap map for
677 private void propagateInsertions(SequenceI profileseq, AlignmentI al,
680 char gc = al.getGapCharacter();
681 // recover mapping between sequence's non-gap positions and positions
683 pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
684 int[] viscontigs = hiddenColumns.getVisibleContigs(0,
685 profileseq.getLength());
689 // add profile to visible contigs
690 for (int v = 0; v < viscontigs.length; v += 2)
692 if (viscontigs[v] > spos)
694 StringBuffer sb = new StringBuffer();
695 for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
699 for (int s = 0, ns = al.getHeight(); s < ns; s++)
701 SequenceI sqobj = al.getSequenceAt(s);
702 if (sqobj != profileseq)
704 String sq = al.getSequenceAt(s).getSequenceAsString();
705 if (sq.length() <= spos + offset)
708 int diff = spos + offset - sq.length() - 1;
713 while ((diff = spos + offset - sq.length() - 1) > 0)
716 // + ((diff >= sb.length()) ? sb.toString() : sb
717 // .substring(0, diff));
718 if (diff >= sb.length())
724 char[] buf = new char[diff];
725 sb.getChars(0, diff, buf, 0);
726 sq += buf.toString();
734 al.getSequenceAt(s).setSequence(
735 sq.substring(0, spos + offset) + sb.toString()
736 + sq.substring(spos + offset));
740 // offset+=sb.length();
742 spos = viscontigs[v + 1] + 1;
744 if ((offset + spos) < profileseq.getLength())
746 // pad the final region with gaps.
747 StringBuffer sb = new StringBuffer();
748 for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
752 for (int s = 0, ns = al.getHeight(); s < ns; s++)
754 SequenceI sqobj = al.getSequenceAt(s);
755 if (sqobj == profileseq)
759 String sq = sqobj.getSequenceAsString();
761 int diff = origseq.getLength() - sq.length();
765 // + ((diff >= sb.length()) ? sb.toString() : sb
766 // .substring(0, diff));
767 if (diff >= sb.length())
773 char[] buf = new char[diff];
774 sb.getChars(0, diff, buf, 0);
775 sq += buf.toString();
777 diff = origseq.getLength() - sq.length();
785 * @return true if there are columns marked
787 public boolean hasSelectedColumns()
789 return (selection != null && selection.size() > 0);
794 public boolean filterAnnotations(Annotation[] annotations,
795 AnnotationFilterParameter filterParams)
797 // JBPNote - this method needs to be refactored to become independent of
799 hiddenColumns.revealAllHiddenColumns(this);
804 if (annotations[count] != null)
807 boolean itemMatched = false;
809 if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
810 && annotations[count].value >= filterParams
811 .getThresholdValue())
815 if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
816 && annotations[count].value <= filterParams
817 .getThresholdValue())
822 if (filterParams.isFilterAlphaHelix()
823 && annotations[count].secondaryStructure == 'H')
828 if (filterParams.isFilterBetaSheet()
829 && annotations[count].secondaryStructure == 'E')
834 if (filterParams.isFilterTurn()
835 && annotations[count].secondaryStructure == 'S')
840 String regexSearchString = filterParams.getRegexString();
841 if (regexSearchString != null
842 && !filterParams.getRegexSearchFields().isEmpty())
844 List<SearchableAnnotationField> fields = filterParams
845 .getRegexSearchFields();
848 if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
849 && annotations[count].displayCharacter
850 .matches(regexSearchString))
854 } catch (java.util.regex.PatternSyntaxException pse)
856 if (annotations[count].displayCharacter
857 .equals(regexSearchString))
862 if (fields.contains(SearchableAnnotationField.DESCRIPTION)
863 && annotations[count].description != null
864 && annotations[count].description
865 .matches(regexSearchString))
873 this.addElement(count);
877 } while (count < annotations.length);
882 * Returns a hashCode built from selected columns and hidden column ranges
885 public int hashCode()
887 int hashCode = selection.hashCode();
888 return hiddenColumns.hashCode(hashCode);
892 * Answers true if comparing to a ColumnSelection with the same selected
893 * columns and hidden columns, else false
896 public boolean equals(Object obj)
898 if (!(obj instanceof ColumnSelection))
902 ColumnSelection that = (ColumnSelection) obj;
905 * check columns selected are either both null, or match
907 if (this.selection == null)
909 if (that.selection != null)
914 if (!this.selection.equals(that.selection))
919 return this.hiddenColumns.equals(that.hiddenColumns);
923 * Updates the column selection depending on the parameters, and returns true
924 * if any change was made to the selection
926 * @param markedColumns
927 * a set identifying marked columns (base 0)
929 * the first column of the range to operate over (base 0)
931 * the last column of the range to operate over (base 0)
933 * if true, deselect marked columns and select unmarked
934 * @param extendCurrent
935 * if true, extend rather than replacing the current column selection
937 * if true, toggle the selection state of marked columns
941 public boolean markColumns(BitSet markedColumns, int startCol,
942 int endCol, boolean invert, boolean extendCurrent, boolean toggle)
944 boolean changed = false;
945 if (!extendCurrent && !toggle)
947 changed = !this.isEmpty();
952 // invert only in the currently selected sequence region
953 int i = markedColumns.nextClearBit(startCol);
954 int ibs = markedColumns.nextSetBit(startCol);
955 while (i >= startCol && i <= endCol)
957 if (ibs < 0 || i < ibs)
960 if (toggle && contains(i))
971 i = markedColumns.nextClearBit(ibs);
972 ibs = markedColumns.nextSetBit(i);
978 int i = markedColumns.nextSetBit(startCol);
979 while (i >= startCol && i <= endCol)
982 if (toggle && contains(i))
990 i = markedColumns.nextSetBit(i + 1);
997 * Adjusts column selections, and the given selection group, to match the
998 * range of a stretch (e.g. mouse drag) operation
1000 * Method refactored from ScalePanel.mouseDragged
1003 * current column position, adjusted for hidden columns
1005 * current selection group
1007 * start position of the stretch group
1009 * end position of the stretch group
1011 public void stretchGroup(int res, SequenceGroup sg, int min, int max)
1018 if (res > sg.getStartRes())
1020 // expand selection group to the right
1023 if (res < sg.getStartRes())
1025 // expand selection group to the left
1026 sg.setStartRes(res);
1030 * expand or shrink column selection to match the
1031 * range of the drag operation
1033 for (int col = min; col <= max; col++)
1035 if (col < sg.getStartRes() || col > sg.getEndRes())
1037 // shrinking drag - remove from selection
1042 // expanding drag - add to selection