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
345 * The list contains no duplicates but is not necessarily ordered. Columns are
346 * reported in alignment coordinates (base 1), so may also include columns
347 * hidden from the current view. To modify (for example sort) the list, you
348 * should first make a copy.
350 * The list is not thread-safe: iterating over it could result in
351 * ConcurrentModificationException if it is modified by another thread.
353 public List<Integer> getSelected()
355 return selection.getList();
359 * @return list of int arrays containing start and end column position for
360 * runs of selected columns ordered from right to left.
362 public List<int[]> getSelectedRanges()
364 return selection.getRanges();
370 * index to search for in column selection
372 * @return true if col is selected
374 public boolean contains(int col)
376 return (col > -1) ? selection.isSelected(col) : false;
382 public boolean intersects(int from, int to)
384 // TODO: do this in a more efficient bitwise way
385 for (int f = from; f <= to; f++)
387 if (selection.isSelected(f))
396 * Answers true if no columns are selected, else false
398 public boolean isEmpty()
400 return selection == null || selection.isEmpty();
404 * rightmost selected column
406 * @return rightmost column in alignment that is selected
410 if (selection.isEmpty())
414 return selection.getMaxColumn();
418 * Leftmost column in selection
420 * @return column index of leftmost column in selection
424 if (selection.isEmpty())
428 return selection.getMinColumn();
431 public void hideSelectedColumns(AlignmentI al)
433 synchronized (selection)
435 for (int[] selregions : selection.getRanges())
437 al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
445 * Hides the specified column and any adjacent selected columns
450 public void hideSelectedColumns(int col, HiddenColumns hidden)
453 * deselect column (whether selected or not!)
458 * find adjacent selected columns
460 int min = col - 1, max = col + 1;
461 while (contains(min))
467 while (contains(max))
474 * min, max are now the closest unselected columns
483 hidden.hideColumns(min, max);
491 public ColumnSelection(ColumnSelection copy)
495 selection = new IntList(copy.selection);
502 public ColumnSelection()
507 * Invert the column selection from first to end-1. leaves hiddenColumns
508 * untouched (and unselected)
513 public void invertColumnSelection(int first, int width, AlignmentI al)
515 boolean hasHidden = al.getHiddenColumns().hasHiddenColumns();
516 for (int i = first; i < width; i++)
524 if (!hasHidden || al.getHiddenColumns().isVisible(i))
533 * set the selected columns to the given column selection, excluding any
534 * columns that are hidden.
538 public void setElementsFrom(ColumnSelection colsel,
539 HiddenColumns hiddenColumns)
541 selection = new IntList();
542 if (colsel.selection != null && colsel.selection.size() > 0)
544 if (hiddenColumns.hasHiddenColumns())
546 // only select visible columns in this columns selection
547 for (Integer col : colsel.getSelected())
549 if (hiddenColumns != null
550 && hiddenColumns.isVisible(col.intValue()))
558 // add everything regardless
559 for (Integer col : colsel.getSelected())
569 * @return true if there are columns marked
571 public boolean hasSelectedColumns()
573 return (selection != null && selection.size() > 0);
577 * Selects columns where the given annotation matches the provided filter
578 * condition(s). Any existing column selections are first cleared. Answers the
579 * number of columns added.
582 * @param filterParams
585 public int filterAnnotations(AlignmentAnnotation ann_row,
586 AnnotationFilterParameter filterParams)
588 Annotation[] annotations = ann_row.annotations;
589 // JBPNote - this method needs to be refactored to become independent of
593 if (ann_row.graph == AlignmentAnnotation.CONTACT_MAP && (filterParams
594 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
596 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD))
598 float tVal = filterParams.getThresholdValue();
599 if (ann_row.sequenceRef != null)
601 // TODO - get ContactList from AlignmentView for non-seq-ref associatd
602 for (int column = 0; column < annotations.length; column++)
604 if (ann_row.annotations[column] == null)
609 int cpos = ann_row.sequenceRef.findPosition(column) - 1;
610 ContactListI clist = ann_row.sequenceRef
611 .getContactListFor(ann_row, cpos);
612 for (int row = column + 8, rowEnd = clist
613 .getContactHeight(); row < rowEnd; row++)
616 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
617 ? (clist.getContactAt(row) > tVal)
618 : (clist.getContactAt(row) < tVal))
622 // int column_forrowpos = ann_row.sequenceRef.findIndex(row + 1);
623 // addElement(column_forrowpos);
628 return selection.size();
635 Annotation ann = annotations[column];
638 float value = ann.value;
639 boolean matched = false;
642 * filter may have multiple conditions -
643 * these are or'd until a match is found
646 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
647 && value > filterParams.getThresholdValue())
652 if (!matched && filterParams
653 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
654 && value < filterParams.getThresholdValue())
659 if (!matched && filterParams.isFilterAlphaHelix()
660 && ann.secondaryStructure == 'H')
665 if (!matched && filterParams.isFilterBetaSheet()
666 && ann.secondaryStructure == 'E')
671 if (!matched && filterParams.isFilterTurn()
672 && ann.secondaryStructure == 'S')
677 String regexSearchString = filterParams.getRegexString();
678 if (!matched && regexSearchString != null)
680 List<SearchableAnnotationField> fields = filterParams
681 .getRegexSearchFields();
682 for (SearchableAnnotationField field : fields)
684 String compareTo = field == SearchableAnnotationField.DISPLAY_STRING
685 ? ann.displayCharacter // match 'Label'
686 : ann.description; // and/or 'Description'
687 if (compareTo != null)
691 if (compareTo.matches(regexSearchString))
695 } catch (PatternSyntaxException pse)
697 if (compareTo.equals(regexSearchString))
712 this.addElement(column);
717 } while (column < annotations.length);
723 * Returns a hashCode built from selected columns ranges
726 public int hashCode()
728 return selection.hashCode();
732 * Answers true if comparing to a ColumnSelection with the same selected
733 * columns and hidden columns, else false
736 public boolean equals(Object obj)
738 if (!(obj instanceof ColumnSelection))
742 ColumnSelection that = (ColumnSelection) obj;
745 * check columns selected are either both null, or match
747 if (this.selection == null)
749 if (that.selection != null)
754 if (!this.selection.equals(that.selection))
763 * Updates the column selection depending on the parameters, and returns true
764 * if any change was made to the selection
766 * @param markedColumns
767 * a set identifying marked columns (base 0)
769 * the first column of the range to operate over (base 0)
771 * the last column of the range to operate over (base 0)
773 * if true, deselect marked columns and select unmarked
774 * @param extendCurrent
775 * if true, extend rather than replacing the current column selection
777 * if true, toggle the selection state of marked columns
781 public boolean markColumns(BitSet markedColumns, int startCol, int endCol,
782 boolean invert, boolean extendCurrent, boolean toggle)
784 boolean changed = false;
785 if (!extendCurrent && !toggle)
787 changed = !this.isEmpty();
792 // invert only in the currently selected sequence region
793 int i = markedColumns.nextClearBit(startCol);
794 int ibs = markedColumns.nextSetBit(startCol);
795 while (i >= startCol && i <= endCol)
797 if (ibs < 0 || i < ibs)
800 if (toggle && contains(i))
811 i = markedColumns.nextClearBit(ibs);
812 ibs = markedColumns.nextSetBit(i);
818 int i = markedColumns.nextSetBit(startCol);
819 while (i >= startCol && i <= endCol)
822 if (toggle && contains(i))
830 i = markedColumns.nextSetBit(i + 1);
837 * Adjusts column selections, and the given selection group, to match the
838 * range of a stretch (e.g. mouse drag) operation
840 * Method refactored from ScalePanel.mouseDragged
843 * current column position, adjusted for hidden columns
845 * current selection group
847 * start position of the stretch group
849 * end position of the stretch group
851 public void stretchGroup(int res, SequenceGroup sg, int min, int max)
858 if (res > sg.getStartRes())
860 // expand selection group to the right
863 if (res < sg.getStartRes())
865 // expand selection group to the left
870 * expand or shrink column selection to match the
871 * range of the drag operation
873 for (int col = min; col <= max; col++)
875 if (col < sg.getStartRes() || col > sg.getEndRes())
877 // shrinking drag - remove from selection
882 // expanding drag - add to selection