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
282 * @param rng [start_0, end_0, start_1, end_1, ... ]
283 * @param baseOne - when true, ranges are base 1 and will be mapped to base 0
285 public void addRangeOfElements(int[] rng,boolean baseOne)
287 int base=baseOne ? -1 : 0;
288 for (int c = 0; c < rng.length; c += 2)
290 for (int p = rng[c]; p <= rng[c + 1]; p++)
292 selection.add(base+p);
298 * clears column selection
306 * Removes value 'col' from the selection (not the col'th item)
309 * index of column to be removed
311 public void removeElement(int col)
313 selection.remove(col);
317 * removes a range of columns from the selection
320 * int - first column in range to be removed
324 public void removeElements(int start, int end)
327 for (int i = start; i < end; i++)
329 colInt = Integer.valueOf(i);
330 if (selection.contains(colInt))
332 selection.remove(colInt);
338 * Returns a read-only view of the (possibly empty) list of selected columns
340 * The list contains no duplicates but is not necessarily ordered. It also may
341 * include columns hidden from the current view. To modify (for example sort)
342 * the list, you should first make a copy.
344 * The list is not thread-safe: iterating over it could result in
345 * ConcurrentModificationException if it is modified by another thread.
347 public List<Integer> getSelected()
349 return selection.getList();
353 * @return list of int arrays containing start and end column position for
354 * runs of selected columns ordered from right to left.
356 public List<int[]> getSelectedRanges()
358 return selection.getRanges();
364 * index to search for in column selection
366 * @return true if col is selected
368 public boolean contains(int col)
370 return (col > -1) ? selection.isSelected(col) : false;
376 public boolean intersects(int from, int to)
378 // TODO: do this in a more efficient bitwise way
379 for (int f = from; f <= to; f++)
381 if (selection.isSelected(f))
390 * Answers true if no columns are selected, else false
392 public boolean isEmpty()
394 return selection == null || selection.isEmpty();
398 * rightmost selected column
400 * @return rightmost column in alignment that is selected
404 if (selection.isEmpty())
408 return selection.getMaxColumn();
412 * Leftmost column in selection
414 * @return column index of leftmost column in selection
418 if (selection.isEmpty())
422 return selection.getMinColumn();
425 public void hideSelectedColumns(AlignmentI al)
427 synchronized (selection)
429 for (int[] selregions : selection.getRanges())
431 al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
439 * Hides the specified column and any adjacent selected columns
444 public void hideSelectedColumns(int col, HiddenColumns hidden)
447 * deselect column (whether selected or not!)
452 * find adjacent selected columns
454 int min = col - 1, max = col + 1;
455 while (contains(min))
461 while (contains(max))
468 * min, max are now the closest unselected columns
477 hidden.hideColumns(min, max);
485 public ColumnSelection(ColumnSelection copy)
489 selection = new IntList(copy.selection);
496 public ColumnSelection()
501 * Invert the column selection from first to end-1. leaves hiddenColumns
502 * untouched (and unselected)
507 public void invertColumnSelection(int first, int width, AlignmentI al)
509 boolean hasHidden = al.getHiddenColumns().hasHiddenColumns();
510 for (int i = first; i < width; i++)
518 if (!hasHidden || al.getHiddenColumns().isVisible(i))
527 * set the selected columns to the given column selection, excluding any
528 * columns that are hidden.
532 public void setElementsFrom(ColumnSelection colsel,
533 HiddenColumns hiddenColumns)
535 selection = new IntList();
536 if (colsel.selection != null && colsel.selection.size() > 0)
538 if (hiddenColumns.hasHiddenColumns())
540 // only select visible columns in this columns selection
541 for (Integer col : colsel.getSelected())
543 if (hiddenColumns != null
544 && hiddenColumns.isVisible(col.intValue()))
552 // add everything regardless
553 for (Integer col : colsel.getSelected())
563 * @return true if there are columns marked
565 public boolean hasSelectedColumns()
567 return (selection != null && selection.size() > 0);
571 * Selects columns where the given annotation matches the provided filter
572 * condition(s). Any existing column selections are first cleared. Answers the
573 * number of columns added.
576 * @param filterParams
579 public int filterAnnotations(AlignmentAnnotation ann_row,
580 AnnotationFilterParameter filterParams)
582 Annotation[] annotations = ann_row.annotations;
583 // JBPNote - this method needs to be refactored to become independent of
587 if (ann_row.graph == AlignmentAnnotation.CONTACT_MAP && (filterParams
588 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
590 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD))
592 float tVal = filterParams.getThresholdValue();
593 if (ann_row.sequenceRef != null)
595 // TODO - get ContactList from AlignmentView for non-seq-ref associatd
596 for (int column = 0; column < annotations.length; column++)
598 if (ann_row.annotations[column] == null)
603 int cpos = ann_row.sequenceRef.findPosition(column) - 1;
604 ContactListI clist = ann_row.sequenceRef
605 .getContactListFor(ann_row, cpos);
606 for (int row = column + 8,
607 rowEnd = clist.getContactHeight(); row < rowEnd; row++)
610 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
611 ? (clist.getContactAt(row) > tVal)
612 : (clist.getContactAt(row) < tVal))
616 // int column_forrowpos = ann_row.sequenceRef.findIndex(row + 1);
617 // addElement(column_forrowpos);
622 return selection.size();
629 Annotation ann = annotations[column];
632 float value = ann.value;
633 boolean matched = false;
636 * filter may have multiple conditions -
637 * these are or'd until a match is found
640 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
641 && value > filterParams.getThresholdValue())
646 if (!matched && filterParams
647 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
648 && value < filterParams.getThresholdValue())
653 if (!matched && filterParams.isFilterAlphaHelix()
654 && ann.secondaryStructure == 'H')
659 if (!matched && filterParams.isFilterBetaSheet()
660 && ann.secondaryStructure == 'E')
665 if (!matched && filterParams.isFilterTurn()
666 && ann.secondaryStructure == 'S')
671 String regexSearchString = filterParams.getRegexString();
672 if (!matched && regexSearchString != null)
674 List<SearchableAnnotationField> fields = filterParams
675 .getRegexSearchFields();
676 for (SearchableAnnotationField field : fields)
678 String compareTo = field == SearchableAnnotationField.DISPLAY_STRING
679 ? ann.displayCharacter // match 'Label'
680 : ann.description; // and/or 'Description'
681 if (compareTo != null)
685 if (compareTo.matches(regexSearchString))
689 } catch (PatternSyntaxException pse)
691 if (compareTo.equals(regexSearchString))
706 this.addElement(column);
711 } while (column < annotations.length);
717 * Returns a hashCode built from selected columns ranges
720 public int hashCode()
722 return selection.hashCode();
726 * Answers true if comparing to a ColumnSelection with the same selected
727 * columns and hidden columns, else false
730 public boolean equals(Object obj)
732 if (!(obj instanceof ColumnSelection))
736 ColumnSelection that = (ColumnSelection) obj;
739 * check columns selected are either both null, or match
741 if (this.selection == null)
743 if (that.selection != null)
748 if (!this.selection.equals(that.selection))
757 * Updates the column selection depending on the parameters, and returns true
758 * if any change was made to the selection
760 * @param markedColumns
761 * a set identifying marked columns (base 0)
763 * the first column of the range to operate over (base 0)
765 * the last column of the range to operate over (base 0)
767 * if true, deselect marked columns and select unmarked
768 * @param extendCurrent
769 * if true, extend rather than replacing the current column selection
771 * if true, toggle the selection state of marked columns
775 public boolean markColumns(BitSet markedColumns, int startCol, int endCol,
776 boolean invert, boolean extendCurrent, boolean toggle)
778 boolean changed = false;
779 if (!extendCurrent && !toggle)
781 changed = !this.isEmpty();
786 // invert only in the currently selected sequence region
787 int i = markedColumns.nextClearBit(startCol);
788 int ibs = markedColumns.nextSetBit(startCol);
789 while (i >= startCol && i <= endCol)
791 if (ibs < 0 || i < ibs)
794 if (toggle && contains(i))
805 i = markedColumns.nextClearBit(ibs);
806 ibs = markedColumns.nextSetBit(i);
812 int i = markedColumns.nextSetBit(startCol);
813 while (i >= startCol && i <= endCol)
816 if (toggle && contains(i))
824 i = markedColumns.nextSetBit(i + 1);
831 * Adjusts column selections, and the given selection group, to match the
832 * range of a stretch (e.g. mouse drag) operation
834 * Method refactored from ScalePanel.mouseDragged
837 * current column position, adjusted for hidden columns
839 * current selection group
841 * start position of the stretch group
843 * end position of the stretch group
845 public void stretchGroup(int res, SequenceGroup sg, int min, int max)
852 if (res > sg.getStartRes())
854 // expand selection group to the right
857 if (res < sg.getStartRes())
859 // expand selection group to the left
864 * expand or shrink column selection to match the
865 * range of the drag operation
867 for (int col = min; col <= max; col++)
869 if (col < sg.getStartRes() || col > sg.getEndRes())
871 // shrinking drag - remove from selection
876 // expanding drag - add to selection