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 * clears column selection
289 * Removes value 'col' from the selection (not the col'th item)
292 * index of column to be removed
294 public void removeElement(int col)
296 selection.remove(col);
300 * removes a range of columns from the selection
303 * int - first column in range to be removed
307 public void removeElements(int start, int end)
310 for (int i = start; i < end; i++)
312 colInt = Integer.valueOf(i);
313 if (selection.contains(colInt))
315 selection.remove(colInt);
321 * Returns a read-only view of the (possibly empty) list of selected columns
323 * The list contains no duplicates but is not necessarily ordered. It also may
324 * include columns hidden from the current view. To modify (for example sort)
325 * the list, you should first make a copy.
327 * The list is not thread-safe: iterating over it could result in
328 * ConcurrentModificationException if it is modified by another thread.
330 public List<Integer> getSelected()
332 return selection.getList();
336 * @return list of int arrays containing start and end column position for
337 * runs of selected columns ordered from right to left.
339 public List<int[]> getSelectedRanges()
341 return selection.getRanges();
347 * index to search for in column selection
349 * @return true if col is selected
351 public boolean contains(int col)
353 return (col > -1) ? selection.isSelected(col) : false;
359 public boolean intersects(int from, int to)
361 // TODO: do this in a more efficient bitwise way
362 for (int f = from; f <= to; f++)
364 if (selection.isSelected(f))
373 * Answers true if no columns are selected, else false
375 public boolean isEmpty()
377 return selection == null || selection.isEmpty();
381 * rightmost selected column
383 * @return rightmost column in alignment that is selected
387 if (selection.isEmpty())
391 return selection.getMaxColumn();
395 * Leftmost column in selection
397 * @return column index of leftmost column in selection
401 if (selection.isEmpty())
405 return selection.getMinColumn();
408 public void hideSelectedColumns(AlignmentI al)
410 synchronized (selection)
412 for (int[] selregions : selection.getRanges())
414 al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
422 * Hides the specified column and any adjacent selected columns
427 public void hideSelectedColumns(int col, HiddenColumns hidden)
430 * deselect column (whether selected or not!)
435 * find adjacent selected columns
437 int min = col - 1, max = col + 1;
438 while (contains(min))
444 while (contains(max))
451 * min, max are now the closest unselected columns
460 hidden.hideColumns(min, max);
468 public ColumnSelection(ColumnSelection copy)
472 selection = new IntList(copy.selection);
479 public ColumnSelection()
484 * Invert the column selection from first to end-1. leaves hiddenColumns
485 * untouched (and unselected)
490 public void invertColumnSelection(int first, int width, AlignmentI al)
492 boolean hasHidden = al.getHiddenColumns().hasHiddenColumns();
493 for (int i = first; i < width; i++)
501 if (!hasHidden || al.getHiddenColumns().isVisible(i))
510 * set the selected columns to the given column selection, excluding any
511 * columns that are hidden.
515 public void setElementsFrom(ColumnSelection colsel,
516 HiddenColumns hiddenColumns)
518 selection = new IntList();
519 if (colsel.selection != null && colsel.selection.size() > 0)
521 if (hiddenColumns.hasHiddenColumns())
523 // only select visible columns in this columns selection
524 for (Integer col : colsel.getSelected())
526 if (hiddenColumns != null
527 && hiddenColumns.isVisible(col.intValue()))
535 // add everything regardless
536 for (Integer col : colsel.getSelected())
546 * @return true if there are columns marked
548 public boolean hasSelectedColumns()
550 return (selection != null && selection.size() > 0);
554 * Selects columns where the given annotation matches the provided filter
555 * condition(s). Any existing column selections are first cleared. Answers the
556 * number of columns added.
559 * @param filterParams
562 public int filterAnnotations(AlignmentAnnotation ann_row,
563 AnnotationFilterParameter filterParams)
565 Annotation[] annotations = ann_row.annotations;
566 // JBPNote - this method needs to be refactored to become independent of
570 if (ann_row.graph == AlignmentAnnotation.CONTACT_MAP && (filterParams
571 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
573 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD))
575 float tVal = filterParams.getThresholdValue();
576 if (ann_row.sequenceRef != null)
578 // TODO - get ContactList from AlignmentView for non-seq-ref associatd
579 for (int column = 0; column < annotations.length; column++)
581 if (ann_row.annotations[column] == null)
586 int cpos = ann_row.sequenceRef.findPosition(column) - 1;
587 ContactListI clist = ann_row.sequenceRef
588 .getContactListFor(ann_row, cpos);
589 for (int row = column + 8, rowEnd = clist
590 .getContactHeight(); row < rowEnd; row++)
593 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
594 ? (clist.getContactAt(row) > tVal)
595 : (clist.getContactAt(row) < tVal))
599 // int column_forrowpos = ann_row.sequenceRef.findIndex(row + 1);
600 // addElement(column_forrowpos);
605 return selection.size();
612 Annotation ann = annotations[column];
615 float value = ann.value;
616 boolean matched = false;
619 * filter may have multiple conditions -
620 * these are or'd until a match is found
623 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
624 && value > filterParams.getThresholdValue())
629 if (!matched && filterParams
630 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
631 && value < filterParams.getThresholdValue())
636 if (!matched && filterParams.isFilterAlphaHelix()
637 && ann.secondaryStructure == 'H')
642 if (!matched && filterParams.isFilterBetaSheet()
643 && ann.secondaryStructure == 'E')
648 if (!matched && filterParams.isFilterTurn()
649 && ann.secondaryStructure == 'S')
654 String regexSearchString = filterParams.getRegexString();
655 if (!matched && regexSearchString != null)
657 List<SearchableAnnotationField> fields = filterParams
658 .getRegexSearchFields();
659 for (SearchableAnnotationField field : fields)
661 String compareTo = field == SearchableAnnotationField.DISPLAY_STRING
662 ? ann.displayCharacter // match 'Label'
663 : ann.description; // and/or 'Description'
664 if (compareTo != null)
668 if (compareTo.matches(regexSearchString))
672 } catch (PatternSyntaxException pse)
674 if (compareTo.equals(regexSearchString))
689 this.addElement(column);
694 } while (column < annotations.length);
700 * Returns a hashCode built from selected columns ranges
703 public int hashCode()
705 return selection.hashCode();
709 * Answers true if comparing to a ColumnSelection with the same selected
710 * columns and hidden columns, else false
713 public boolean equals(Object obj)
715 if (!(obj instanceof ColumnSelection))
719 ColumnSelection that = (ColumnSelection) obj;
722 * check columns selected are either both null, or match
724 if (this.selection == null)
726 if (that.selection != null)
731 if (!this.selection.equals(that.selection))
740 * Updates the column selection depending on the parameters, and returns true
741 * if any change was made to the selection
743 * @param markedColumns
744 * a set identifying marked columns (base 0)
746 * the first column of the range to operate over (base 0)
748 * the last column of the range to operate over (base 0)
750 * if true, deselect marked columns and select unmarked
751 * @param extendCurrent
752 * if true, extend rather than replacing the current column selection
754 * if true, toggle the selection state of marked columns
758 public boolean markColumns(BitSet markedColumns, int startCol, int endCol,
759 boolean invert, boolean extendCurrent, boolean toggle)
761 boolean changed = false;
762 if (!extendCurrent && !toggle)
764 changed = !this.isEmpty();
769 // invert only in the currently selected sequence region
770 int i = markedColumns.nextClearBit(startCol);
771 int ibs = markedColumns.nextSetBit(startCol);
772 while (i >= startCol && i <= endCol)
774 if (ibs < 0 || i < ibs)
777 if (toggle && contains(i))
788 i = markedColumns.nextClearBit(ibs);
789 ibs = markedColumns.nextSetBit(i);
795 int i = markedColumns.nextSetBit(startCol);
796 while (i >= startCol && i <= endCol)
799 if (toggle && contains(i))
807 i = markedColumns.nextSetBit(i + 1);
814 * Adjusts column selections, and the given selection group, to match the
815 * range of a stretch (e.g. mouse drag) operation
817 * Method refactored from ScalePanel.mouseDragged
820 * current column position, adjusted for hidden columns
822 * current selection group
824 * start position of the stretch group
826 * end position of the stretch group
828 public void stretchGroup(int res, SequenceGroup sg, int min, int max)
835 if (res > sg.getStartRes())
837 // expand selection group to the right
840 if (res < sg.getStartRes())
842 // expand selection group to the left
847 * expand or shrink column selection to match the
848 * range of the drag operation
850 for (int col = min; col <= max; col++)
852 if (col < sg.getStartRes() || col > sg.getEndRes())
854 // shrinking drag - remove from selection
859 // expanding drag - add to selection