3501fce77c2ba26b2d4173f26256cf09edaab89e
[jalview.git] / src / jalview / datamodel / HiddenColumnsCursor.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.datamodel;
22
23 import java.util.ArrayList;
24 import java.util.List;
25
26 public class HiddenColumnsCursor
27 {
28   // absolute position of first hidden column
29   private int firstColumn;
30
31   private List<int[]> hiddenColumns = new ArrayList<>();
32
33   private HiddenCursorPosition cursorPos = new HiddenCursorPosition(0, 0);
34
35   protected HiddenColumnsCursor()
36   {
37
38   }
39
40   protected HiddenColumnsCursor(List<int[]> hiddenCols)
41   {
42     resetCursor(hiddenCols, 0, 0);
43   }
44
45   protected HiddenColumnsCursor(List<int[]> hiddenCols, int index,
46           int hiddencount)
47   {
48     resetCursor(hiddenCols, index, hiddencount);
49   }
50
51   /**
52    * Reset the cursor with a new hidden columns collection, where we know in
53    * advance the index and hidden columns count of a particular location.
54    * 
55    * @param hiddenCols
56    *          new hidden columns collection
57    * @param index
58    *          cursor index to reset to
59    * @param hiddencount
60    *          hidden columns count to reset to
61    */
62   private void resetCursor(List<int[]> hiddenCols, int index,
63           int hiddencount)
64   {
65     hiddenColumns = hiddenCols;
66     if (!hiddenCols.isEmpty())
67     {
68       firstColumn = hiddenColumns.get(0)[0];
69       cursorPos = new HiddenCursorPosition(index,
70               hiddencount);
71     }
72   }
73
74   /**
75    * Delete the region the cursor is currently at. Avoids having to reset the
76    * cursor just because we deleted a region.
77    * 
78    * Calls to updateForDeletedRegion should be made from within a writeLock in
79    * the HiddenColumns class - since changes to the hiddenColumns collection
80    * require a writeLock the lock should already exist.
81    *
82    * @param hiddenCols
83    */
84   protected void updateForDeletedRegion(List<int[]> hiddenCols)
85   {
86
87     if (!hiddenCols.isEmpty())
88     {
89       // if there is a region to the right of the current region,
90       // nothing changes; otherwise
91       // we deleted the last region (index=hiddenCols.size()-1)
92       // or the index was at the end of the alignment (index=hiddenCols.size())
93       HiddenCursorPosition oldpos = cursorPos;
94
95       int index = oldpos.getRegionIndex();
96       if (index >= hiddenColumns.size() - 1)
97       {
98         // deleted last region, index is now end of alignment
99         index = hiddenCols.size();
100
101         cursorPos = new HiddenCursorPosition(index,
102                 oldpos.getHiddenSoFar());
103       }
104     }
105     hiddenColumns = hiddenCols;
106   }
107
108   /**
109    * Get the cursor pointing to the hidden region that column is within (if
110    * column is hidden) or which is to the right of column (if column is
111    * visible). If no hidden columns are to the right, returns a cursor pointing
112    * to an imaginary hidden region beyond the end of the hidden columns
113    * collection (this ensures the count of previous hidden columns is correct).
114    * If hidden columns is empty returns null.
115    * 
116    * @param column
117    *          absolute position of a column in the alignment
118    * @return cursor pointing to hidden region containing the column (if hidden)
119    *         or to the right of the column (if visible)
120    */
121   protected HiddenCursorPosition findRegionForColumn(int column)
122   {
123     if (hiddenColumns.isEmpty())
124     {
125       return null;
126     }
127
128     HiddenCursorPosition oldpos = cursorPos;
129     int index = oldpos.getRegionIndex();
130     int hiddenCount = oldpos.getHiddenSoFar();
131
132     if (index == hiddenColumns.size())
133     {
134       // went past the end of hiddenColumns collection last time
135       index--;
136       int[] region = hiddenColumns.get(index);
137       hiddenCount -= region[1] - region[0] + 1;
138     }
139
140     // this if statement excludes case where column is in current region
141     // - no changes needed
142     if (column < firstColumn)
143     {
144       index = 0;
145       hiddenCount = 0;
146     }
147     // column is after current region
148     else if (column > hiddenColumns.get(index)[1])
149     {
150       // iterate from where we are now, if we're lucky we'll be close by
151       // (but still better than iterating from 0)
152       // stop when we find the region *before* column
153       // i.e. the next region starts after column or if not, ends after column
154       while ((index < hiddenColumns.size())
155               && (column > hiddenColumns.get(index)[1]))
156       {
157         int[] region = hiddenColumns.get(index);
158         hiddenCount += region[1] - region[0] + 1;
159         index++;
160       }
161     }
162     // column is before current region
163     else if (column < hiddenColumns.get(index)[0])
164     {
165       // column is before or in the previous region
166       while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
167       {
168         index--;
169         int[] region = hiddenColumns.get(index);
170         hiddenCount -= region[1] - region[0] + 1;
171       }
172     }
173
174     if (index != oldpos.getRegionIndex()
175             || hiddenCount != oldpos.getHiddenSoFar())
176     {
177       cursorPos = new HiddenCursorPosition(index,
178               hiddenCount);
179       return cursorPos;
180     }
181     return oldpos;
182   }
183
184   /**
185    * Get the cursor pointing to the hidden region just after a visible column
186    * 
187    * @param column
188    *          index of column in *visible* alignment (therefore by definition
189    *          column is visible)
190    * @return cursor pointing to hidden region to the right of the column
191    */
192   protected HiddenCursorPosition findRegionForVisColumn(int column)
193   {
194     if (hiddenColumns.isEmpty())
195     {
196       return null;
197     }
198
199     HiddenCursorPosition oldpos = cursorPos;
200     int index = oldpos.getRegionIndex();
201     int hiddenCount = oldpos.getHiddenSoFar();
202
203     if (column < firstColumn)
204     {
205       index = 0;
206       hiddenCount = 0;
207     }
208     else if ((index < hiddenColumns.size())
209             && (hiddenColumns.get(index)[0] <= column + hiddenCount))
210     {
211       // iterate from where we are now, if we're lucky we'll be close by
212       // (but still better than iterating from 0)
213       while ((index < hiddenColumns.size())
214               && (hiddenColumns.get(index)[0] <= column + hiddenCount))
215       {
216         int[] region = hiddenColumns.get(index);
217         hiddenCount += region[1] - region[0] + 1;
218         index++;
219       }
220     }
221     else
222     {
223       while ((index > 0)
224               && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount))
225       {
226         index--;
227         int[] region = hiddenColumns.get(index);
228         hiddenCount -= region[1] - region[0] + 1;
229       }
230
231     }
232
233     if (index != oldpos.getRegionIndex()
234             || hiddenCount != oldpos.getHiddenSoFar())
235     {
236       cursorPos = new HiddenCursorPosition(index,
237               hiddenCount);
238       return cursorPos;
239     }
240     return oldpos;
241   }
242 }