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.List;
25 import java.util.concurrent.atomic.AtomicReference;
27 public class HiddenColumnsCursor
29 // absolute position of first hidden column
30 private int firstColumn;
32 private List<int[]> hiddenColumns = new ArrayList<>();
34 // AtomicReference to hold the current region index and hidden column count
35 // Could be done with synchronisation but benchmarking shows this way is 2x
37 private final AtomicReference<HiddenCursorPosition> cursorPos = new AtomicReference<>(
38 new HiddenCursorPosition(0, 0));
40 protected HiddenColumnsCursor()
46 * Reset the cursor with a new hidden columns collection. Calls to resetCursor
47 * should be made from within a writeLock in the HiddenColumns class - since
48 * changes to the hiddenColumns collection require a writeLock the lock should
52 * new hidden columns collection
54 protected void resetCursor(List<int[]> hiddenCols)
56 resetCursor(hiddenCols, 0, 0);
60 * Reset the cursor with a new hidden columns collection, where we know in
61 * advance the index and hidden columns count of a particular location.
64 * new hidden columns collection
66 * cursor index to reset to
68 * hidden columns count to reset to
70 protected void resetCursor(List<int[]> hiddenCols, int index,
73 hiddenColumns = hiddenCols;
74 if (!hiddenCols.isEmpty())
76 firstColumn = hiddenColumns.get(0)[0];
77 HiddenCursorPosition oldpos = cursorPos.get();
78 HiddenCursorPosition newpos = new HiddenCursorPosition(index,
80 cursorPos.compareAndSet(oldpos, newpos);
85 * Delete the region the cursor is currently at. Avoids having to reset the
86 * cursor just because we deleted a region.
88 * Calls to updateForDeletedRegion should be made from within a writeLock in
89 * the HiddenColumns class - since changes to the hiddenColumns collection
90 * require a writeLock the lock should already exist.
94 protected void updateForDeletedRegion(List<int[]> hiddenCols)
97 if (!hiddenCols.isEmpty())
99 // if there is a region to the right of the current region,
100 // nothing changes; otherwise
101 // we deleted the last region (index=hiddenCols.size()-1)
102 // or the index was at the end of the alignment (index=hiddenCols.size())
103 HiddenCursorPosition oldpos = cursorPos.get();
105 int index = oldpos.getRegionIndex();
106 if (index >= hiddenColumns.size() - 1)
108 // deleted last region, index is now end of alignment
109 index = hiddenCols.size();
111 HiddenCursorPosition newpos = new HiddenCursorPosition(index,
112 oldpos.getHiddenSoFar());
113 cursorPos.compareAndSet(oldpos, newpos);
116 hiddenColumns = hiddenCols;
120 * Get the cursor pointing to the hidden region that column is within (if
121 * column is hidden) or which is to the right of column (if column is
122 * visible). If no hidden columns are to the right, returns a cursor pointing
123 * to an imaginary hidden region beyond the end of the hidden columns
124 * collection (this ensures the count of previous hidden columns is correct).
125 * If hidden columns is empty returns null.
128 * absolute position of a column in the alignment
129 * @return cursor pointing to hidden region containing the column (if hidden)
130 * or to the right of the column (if visible)
132 protected HiddenCursorPosition findRegionForColumn(int column)
134 if (hiddenColumns.isEmpty())
139 HiddenCursorPosition oldpos = cursorPos.get();
140 int index = oldpos.getRegionIndex();
141 int hiddenCount = oldpos.getHiddenSoFar();
143 if (index == hiddenColumns.size())
145 // went past the end of hiddenColumns collection last time
147 int[] region = hiddenColumns.get(index);
148 hiddenCount -= region[1] - region[0] + 1;
151 // this if statement excludes case where column is in current region
152 // - no changes needed
153 if (column < firstColumn)
158 // column is after current region
159 else if (column > hiddenColumns.get(index)[1])
161 // iterate from where we are now, if we're lucky we'll be close by
162 // (but still better than iterating from 0)
163 // stop when we find the region *before* column
164 // i.e. the next region starts after column or if not, ends after column
165 while ((index < hiddenColumns.size())
166 && (column > hiddenColumns.get(index)[1]))
168 int[] region = hiddenColumns.get(index);
169 hiddenCount += region[1] - region[0] + 1;
173 // column is before current region
174 else if (column < hiddenColumns.get(index)[0])
176 // column is before or in the previous region
177 while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
180 int[] region = hiddenColumns.get(index);
181 hiddenCount -= region[1] - region[0] + 1;
185 if (index != oldpos.getRegionIndex()
186 || hiddenCount != oldpos.getHiddenSoFar())
188 HiddenCursorPosition newpos = new HiddenCursorPosition(index,
190 cursorPos.compareAndSet(oldpos, newpos);
197 * Get the cursor pointing to the hidden region just after a visible column
200 * index of column in *visible* alignment (therefore by definition
202 * @return cursor pointing to hidden region to the right of the column
204 protected HiddenCursorPosition findRegionForVisColumn(int column)
206 if (hiddenColumns.isEmpty())
211 HiddenCursorPosition oldpos = cursorPos.get();
212 int index = oldpos.getRegionIndex();
213 int hiddenCount = oldpos.getHiddenSoFar();
215 if (column < firstColumn)
220 else if ((index < hiddenColumns.size())
221 && (hiddenColumns.get(index)[0] <= column + hiddenCount))
223 // iterate from where we are now, if we're lucky we'll be close by
224 // (but still better than iterating from 0)
225 while ((index < hiddenColumns.size())
226 && (hiddenColumns.get(index)[0] <= column + hiddenCount))
228 int[] region = hiddenColumns.get(index);
229 hiddenCount += region[1] - region[0] + 1;
236 && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount))
239 int[] region = hiddenColumns.get(index);
240 hiddenCount -= region[1] - region[0] + 1;
245 if (index != oldpos.getRegionIndex()
246 || hiddenCount != oldpos.getHiddenSoFar())
248 HiddenCursorPosition newpos = new HiddenCursorPosition(index,
250 cursorPos.compareAndSet(oldpos, newpos);