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.viewmodel.annotationfilter.AnnotationFilterParameter;
24 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
26 import java.util.ArrayList;
27 import java.util.BitSet;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.regex.PatternSyntaxException;
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;
357 * Answers true if no columns are selected, else false
359 public boolean isEmpty()
361 return selection == null || selection.isEmpty();
365 * rightmost selected column
367 * @return rightmost column in alignment that is selected
371 if (selection.isEmpty())
375 return selection.getMaxColumn();
379 * Leftmost column in selection
381 * @return column index of leftmost column in selection
385 if (selection.isEmpty())
389 return selection.getMinColumn();
392 public void hideSelectedColumns(AlignmentI al)
394 synchronized (selection)
396 for (int[] selregions : selection.getRanges())
398 al.getHiddenColumns().hideColumns(selregions[0], selregions[1]);
406 * Hides the specified column and any adjacent selected columns
411 public void hideSelectedColumns(int col, HiddenColumns hidden)
414 * deselect column (whether selected or not!)
419 * find adjacent selected columns
421 int min = col - 1, max = col + 1;
422 while (contains(min))
428 while (contains(max))
435 * min, max are now the closest unselected columns
444 hidden.hideColumns(min, max);
452 public ColumnSelection(ColumnSelection copy)
456 selection = new IntList(copy.selection);
463 public ColumnSelection()
468 * Invert the column selection from first to end-1. leaves hiddenColumns
469 * untouched (and unselected)
474 public void invertColumnSelection(int first, int width, AlignmentI al)
476 boolean hasHidden = al.getHiddenColumns().hasHiddenColumns();
477 for (int i = first; i < width; i++)
485 if (!hasHidden || al.getHiddenColumns().isVisible(i))
494 * set the selected columns to the given column selection, excluding any
495 * columns that are hidden.
499 public void setElementsFrom(ColumnSelection colsel,
500 HiddenColumns hiddenColumns)
502 selection = new IntList();
503 if (colsel.selection != null && colsel.selection.size() > 0)
505 if (hiddenColumns.hasHiddenColumns())
507 // only select visible columns in this columns selection
508 for (Integer col : colsel.getSelected())
510 if (hiddenColumns != null
511 && hiddenColumns.isVisible(col.intValue()))
519 // add everything regardless
520 for (Integer col : colsel.getSelected())
530 * @return true if there are columns marked
532 public boolean hasSelectedColumns()
534 return (selection != null && selection.size() > 0);
538 * Selects columns where the given annotation matches the provided filter
539 * condition(s). Any existing column selections are first cleared. Answers the
540 * number of columns added.
543 * @param filterParams
546 public int filterAnnotations(Annotation[] annotations,
547 AnnotationFilterParameter filterParams)
549 // JBPNote - this method needs to be refactored to become independent of
556 Annotation ann = annotations[column];
559 boolean matched = false;
562 * filter may have multiple conditions -
563 * these are or'd until a match is found
566 .getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
567 && ann.value > filterParams.getThresholdValue())
572 if (!matched && filterParams
573 .getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
574 && ann.value < filterParams.getThresholdValue())
579 if (!matched && filterParams.isFilterAlphaHelix()
580 && ann.secondaryStructure == 'H')
585 if (!matched && filterParams.isFilterBetaSheet()
586 && ann.secondaryStructure == 'E')
591 if (!matched && filterParams.isFilterTurn()
592 && ann.secondaryStructure == 'S')
597 String regexSearchString = filterParams.getRegexString();
598 if (!matched && regexSearchString != null)
600 List<SearchableAnnotationField> fields = filterParams
601 .getRegexSearchFields();
602 for (SearchableAnnotationField field : fields)
604 String compareTo = field == SearchableAnnotationField.DISPLAY_STRING
605 ? ann.displayCharacter // match 'Label'
606 : ann.description; // and/or 'Description'
607 if (compareTo != null)
611 if (compareTo.matches(regexSearchString))
615 } catch (PatternSyntaxException pse)
617 if (compareTo.equals(regexSearchString))
632 this.addElement(column);
637 } while (column < annotations.length);
643 * Returns a hashCode built from selected columns ranges
646 public int hashCode()
648 return selection.hashCode();
652 * Answers true if comparing to a ColumnSelection with the same selected
653 * columns and hidden columns, else false
656 public boolean equals(Object obj)
658 if (!(obj instanceof ColumnSelection))
662 ColumnSelection that = (ColumnSelection) obj;
665 * check columns selected are either both null, or match
667 if (this.selection == null)
669 if (that.selection != null)
674 if (!this.selection.equals(that.selection))
683 * Updates the column selection depending on the parameters, and returns true
684 * if any change was made to the selection
686 * @param markedColumns
687 * a set identifying marked columns (base 0)
689 * the first column of the range to operate over (base 0)
691 * the last column of the range to operate over (base 0)
693 * if true, deselect marked columns and select unmarked
694 * @param extendCurrent
695 * if true, extend rather than replacing the current column selection
697 * if true, toggle the selection state of marked columns
701 public boolean markColumns(BitSet markedColumns, int startCol, int endCol,
702 boolean invert, boolean extendCurrent, boolean toggle)
704 boolean changed = false;
705 if (!extendCurrent && !toggle)
707 changed = !this.isEmpty();
712 // invert only in the currently selected sequence region
713 int i = markedColumns.nextClearBit(startCol);
714 int ibs = markedColumns.nextSetBit(startCol);
715 while (i >= startCol && i <= endCol)
717 if (ibs < 0 || i < ibs)
720 if (toggle && contains(i))
731 i = markedColumns.nextClearBit(ibs);
732 ibs = markedColumns.nextSetBit(i);
738 int i = markedColumns.nextSetBit(startCol);
739 while (i >= startCol && i <= endCol)
742 if (toggle && contains(i))
750 i = markedColumns.nextSetBit(i + 1);
757 * Adjusts column selections, and the given selection group, to match the
758 * range of a stretch (e.g. mouse drag) operation
760 * Method refactored from ScalePanel.mouseDragged
763 * current column position, adjusted for hidden columns
765 * current selection group
767 * start position of the stretch group
769 * end position of the stretch group
771 public void stretchGroup(int res, SequenceGroup sg, int min, int max)
778 if (res > sg.getStartRes())
780 // expand selection group to the right
783 if (res < sg.getStartRes())
785 // expand selection group to the left
790 * expand or shrink column selection to match the
791 * range of the drag operation
793 for (int col = min; col <= max; col++)
795 if (col < sg.getStartRes() || col > sg.getEndRes())
797 // shrinking drag - remove from selection
802 // expanding drag - add to selection