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 java.util.ArrayList;
24 import java.util.BitSet;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.regex.PatternSyntaxException;
29 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
30 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
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<>();
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 = Integer.valueOf(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, Integer.valueOf(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<>();
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 private IntList selection = new IntList();
270 * Add a column to the selection
275 public void addElement(int col)
281 * add a series of start,end (inclusive) ranges to the column selection
284 * [start_0, end_0, start_1, end_1, ... ]
286 * - when true, ranges are base 1 and will be mapped to base 0
288 public void addRangeOfElements(int[] rng, boolean baseOne)
290 int base = baseOne ? -1 : 0;
291 for (int c = 0; c < rng.length; c += 2)
293 for (int p = rng[c]; p <= rng[c + 1]; p++)
295 selection.add(base + p);
302 * clears column selection
310 * Removes value 'col' from the selection (not the col'th item)
313 * index of column to be removed
315 public void removeElement(int col)
317 selection.remove(col);
321 * removes a range of columns from the selection
324 * int - first column in range to be removed
328 public void removeElements(int start, int end)
331 for (int i = start; i < end; i++)
333 colInt = Integer.valueOf(i);
334 if (selection.contains(colInt))
336 selection.remove(colInt);
342 * Returns a read-only view of the (possibly empty) list of selected columns
344 * The list contains no duplicates but is not necessarily ordered. It also may
345 * include columns hidden from the current view. To modify (for example sort)
346 * the list, you should first make a copy.
348 * The list is not thread-safe: iterating over it could result in
349 * ConcurrentModificationException if it is modified by another thread.
351 public List<Integer> getSelected()
353 return selection.getList();
357 * @return list of int arrays containing start and end column position for
358 * runs of selected columns ordered from right to left.
360 public List<int[]> getSelectedRanges()
362 return selection.getRanges();
368 * index to search for in column selection
370 * @return true if col is selected
372 public boolean contains(int col)
374 return (col > -1) ? selection.isSelected(col) : false;
380 public boolean intersects(int from, int to)
382 // TODO: do this in a more efficient bitwise way
383 for (int f = from; f <= to; f++)
385 if (selection.isSelected(f))
394 * Answers true if no columns are selected, else false
396 public boolean isEmpty()
398 return selection == null || selection.isEmpty();
402 * rightmost selected column
404 * @return rightmost column in alignment that is selected
408 if (selection.isEmpty())
412 return selection.getMaxColumn();
416 * Leftmost column in selection
418 * @return column index of leftmost column in selection
422 if (selection.isEmpty())
426 return selection.getMinColumn();
429 public void hideSelectedColumns(AlignmentI al)
431 synchronized (selection)
433 for (int[] selregions : selection.getRanges())
435 al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
443 * Hides the specified column and any adjacent selected columns
448 public void hideSelectedColumns(int col, HiddenColumns hidden)
451 * deselect column (whether selected or not!)
456 * find adjacent selected columns
458 int min = col - 1, max = col + 1;
459 while (contains(min))
465 while (contains(max))
472 * min, max are now the closest unselected columns
481 hidden.hideColumns(min, max);
489 public ColumnSelection(ColumnSelection copy)
493 selection = new IntList(copy.selection);
500 public ColumnSelection()
505 * Invert the column selection from first to end-1. leaves hiddenColumns
506 * untouched (and unselected)
511 public void invertColumnSelection(int first, int width, AlignmentI al)
513 boolean hasHidden = al.getHiddenColumns().hasHiddenColumns();
514 for (int i = first; i < width; i++)
522 if (!hasHidden || al.getHiddenColumns().isVisible(i))
531 * set the selected columns to the given column selection, excluding any
532 * columns that are hidden.
536 public void setElementsFrom(ColumnSelection colsel,
537 HiddenColumns hiddenColumns)
539 selection = new IntList();
540 if (colsel.selection != null && colsel.selection.size() > 0)
542 if (hiddenColumns.hasHiddenColumns())
544 // only select visible columns in this columns selection
545 for (Integer col : colsel.getSelected())
547 if (hiddenColumns != null
548 && hiddenColumns.isVisible(col.intValue()))
556 // add everything regardless
557 for (Integer col : colsel.getSelected())
567 * @return true if there are columns marked
569 public boolean hasSelectedColumns()
571 return (selection != null && selection.size() > 0);
575 * Selects columns where the given annotation matches the provided filter
576 * condition(s). Any existing column selections are first cleared. Answers the
577 * number of columns added.
580 * @param filterParams
583 public int filterAnnotations(AlignmentAnnotation ann_row,
584 AnnotationFilterParameter filterParams)
586 Annotation[] annotations = ann_row.annotations;
587 // JBPNote - this method needs to be refactored to become independent of
591 if (ann_row.graph == AlignmentAnnotation.CONTACT_MAP && (filterParams
592 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
594 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD))
596 float tVal = filterParams.getThresholdValue();
597 if (ann_row.sequenceRef != null)
599 // TODO - get ContactList from AlignmentView for non-seq-ref associatd
600 for (int column = 0; column < annotations.length; column++)
602 if (ann_row.annotations[column] == null)
607 int cpos = ann_row.sequenceRef.findPosition(column) - 1;
608 ContactListI clist = ann_row.sequenceRef
609 .getContactListFor(ann_row, cpos);
610 for (int row = column + 8, rowEnd = clist
611 .getContactHeight(); row < rowEnd; row++)
614 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
615 ? (clist.getContactAt(row) > tVal)
616 : (clist.getContactAt(row) < tVal))
620 // int column_forrowpos = ann_row.sequenceRef.findIndex(row + 1);
621 // addElement(column_forrowpos);
626 return selection.size();
633 Annotation ann = annotations[column];
636 float value = ann.value;
637 boolean matched = false;
640 * filter may have multiple conditions -
641 * these are or'd until a match is found
644 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
645 && value > filterParams.getThresholdValue())
650 if (!matched && filterParams
651 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
652 && value < filterParams.getThresholdValue())
657 if (!matched && filterParams.isFilterAlphaHelix()
658 && ann.secondaryStructure == 'H')
663 if (!matched && filterParams.isFilterBetaSheet()
664 && ann.secondaryStructure == 'E')
669 if (!matched && filterParams.isFilterTurn()
670 && ann.secondaryStructure == 'S')
675 String regexSearchString = filterParams.getRegexString();
676 if (!matched && regexSearchString != null)
678 List<SearchableAnnotationField> fields = filterParams
679 .getRegexSearchFields();
680 for (SearchableAnnotationField field : fields)
682 String compareTo = field == SearchableAnnotationField.DISPLAY_STRING
683 ? ann.displayCharacter // match 'Label'
684 : ann.description; // and/or 'Description'
685 if (compareTo != null)
689 if (compareTo.matches(regexSearchString))
693 } catch (PatternSyntaxException pse)
695 if (compareTo.equals(regexSearchString))
710 this.addElement(column);
715 } while (column < annotations.length);
721 * Returns a hashCode built from selected columns ranges
724 public int hashCode()
726 return selection.hashCode();
730 * Answers true if comparing to a ColumnSelection with the same selected
731 * columns and hidden columns, else false
734 public boolean equals(Object obj)
736 if (!(obj instanceof ColumnSelection))
740 ColumnSelection that = (ColumnSelection) obj;
743 * check columns selected are either both null, or match
745 if (this.selection == null)
747 if (that.selection != null)
752 if (!this.selection.equals(that.selection))
761 * Updates the column selection depending on the parameters, and returns true
762 * if any change was made to the selection
764 * @param markedColumns
765 * a set identifying marked columns (base 0)
767 * the first column of the range to operate over (base 0)
769 * the last column of the range to operate over (base 0)
771 * if true, deselect marked columns and select unmarked
772 * @param extendCurrent
773 * if true, extend rather than replacing the current column selection
775 * if true, toggle the selection state of marked columns
779 public boolean markColumns(BitSet markedColumns, int startCol, int endCol,
780 boolean invert, boolean extendCurrent, boolean toggle)
782 boolean changed = false;
783 if (!extendCurrent && !toggle)
785 changed = !this.isEmpty();
790 // invert only in the currently selected sequence region
791 int i = markedColumns.nextClearBit(startCol);
792 int ibs = markedColumns.nextSetBit(startCol);
793 while (i >= startCol && i <= endCol)
795 if (ibs < 0 || i < ibs)
798 if (toggle && contains(i))
809 i = markedColumns.nextClearBit(ibs);
810 ibs = markedColumns.nextSetBit(i);
816 int i = markedColumns.nextSetBit(startCol);
817 while (i >= startCol && i <= endCol)
820 if (toggle && contains(i))
828 i = markedColumns.nextSetBit(i + 1);
835 * Adjusts column selections, and the given selection group, to match the
836 * range of a stretch (e.g. mouse drag) operation
838 * Method refactored from ScalePanel.mouseDragged
841 * current column position, adjusted for hidden columns
843 * current selection group
845 * start position of the stretch group
847 * end position of the stretch group
849 public void stretchGroup(int res, SequenceGroup sg, int min, int max)
856 if (res > sg.getStartRes())
858 // expand selection group to the right
861 if (res < sg.getStartRes())
863 // expand selection group to the left
868 * expand or shrink column selection to match the
869 * range of the drag operation
871 for (int col = min; col <= max; col++)
873 if (col < sg.getStartRes() || col > sg.getEndRes())
875 // shrinking drag - remove from selection
880 // expanding drag - add to selection