572f9991d697011d79f7b24590c27b09ad65c603
[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    *          replacement list of hidden column regions
84    * @param remove
85    *          number of columns which were deleted
86    */
87   protected void updateForDeletedRegion(List<int[]> hiddenCols, int remove)
88   {
89     hiddenColumns = hiddenCols;
90     if (!hiddenCols.isEmpty())
91     {
92       if (cursorPos.getRegionIndex() >= hiddenCols.size())
93       {
94         cursorPos = new HiddenCursorPosition(hiddenCols.size(),
95                 cursorPos.getHiddenSoFar() - remove);
96       }
97     }
98   }
99
100   /**
101    * Get the cursor pointing to the hidden region that column is within (if
102    * column is hidden) or which is to the right of column (if column is
103    * visible). If no hidden columns are to the right, returns a cursor pointing
104    * to an imaginary hidden region beyond the end of the hidden columns
105    * collection (this ensures the count of previous hidden columns is correct).
106    * If hidden columns is empty returns null.
107    * 
108    * @param column
109    *          index of column in visible or absolute coordinates
110    * @param useVisible
111    *          true if column is in visible coordinates, false if absolute
112    * @return cursor pointing to hidden region containing the column (if hidden)
113    *         or to the right of the column (if visible)
114    */
115   protected HiddenCursorPosition findRegionForColumn(int column,
116           boolean useVisible)
117   {
118     if (hiddenColumns.isEmpty())
119     {
120       return null;
121     }
122
123     // used to add in hiddenColumns offset when working with visible columns
124     int offset = (useVisible ? 1 : 0);
125
126     HiddenCursorPosition pos = cursorPos;
127     int index = pos.getRegionIndex();
128     int hiddenCount = pos.getHiddenSoFar();
129
130     if (column < firstColumn)
131     {
132       pos = new HiddenCursorPosition(0, 0);
133     }
134
135     // column is after current region
136     else if ((index < hiddenColumns.size())
137             && (hiddenColumns.get(index)[0] <= column
138                     + offset * hiddenCount))
139     {
140       // iterate from where we are now, if we're lucky we'll be close by
141       // (but still better than iterating from 0)
142       // stop when we find the region *before* column
143       // i.e. the next region starts after column or if not, ends after column
144       pos = searchForward(pos, column, useVisible);
145     }
146
147     // column is before current region
148     else
149     {
150       pos = searchBackward(pos, column, useVisible);
151     }
152     cursorPos = pos;
153     return pos;
154   }
155
156   /**
157    * Search forwards through the hidden columns collection to find the hidden
158    * region immediately before a column
159    * 
160    * @param pos
161    *          current position
162    * @param column
163    *          column to locate
164    * @param useVisible
165    *          whether using visible or absolute coordinates
166    * @return position of region before column
167    */
168   private HiddenCursorPosition searchForward(HiddenCursorPosition pos,
169           int column, boolean useVisible)
170   {
171     HiddenCursorPosition p = pos;
172     if (useVisible)
173     {
174       while ((p.getRegionIndex() < hiddenColumns.size())
175               && hiddenColumns.get(p.getRegionIndex())[0] <= column
176                       + p.getHiddenSoFar())
177       {
178         p = stepForward(p);
179       }
180     }
181     else
182     {
183       while ((p.getRegionIndex() < hiddenColumns.size())
184               && hiddenColumns.get(p.getRegionIndex())[1] < column)
185       {
186         p = stepForward(p);
187       }
188     }
189     return p;
190   }
191
192   /**
193    * Move to the next (rightwards) hidden region after a given cursor position
194    * 
195    * @param p
196    *          current position of cursor
197    * @return new position of cursor at next region
198    */
199   private HiddenCursorPosition stepForward(HiddenCursorPosition p)
200   {
201     int[] region = hiddenColumns.get(p.getRegionIndex());
202
203     // increment the index, and add this region's hidden columns to the hidden
204     // column count
205     return new HiddenCursorPosition(p.getRegionIndex() + 1,
206             p.getHiddenSoFar() + region[1] - region[0] + 1);
207   }
208
209   /**
210    * Search backwards through the hidden columns collection to find the hidden
211    * region immediately before (left of) a given column
212    * 
213    * @param pos
214    *          current position
215    * @param column
216    *          column to locate
217    * @param useVisible
218    *          whether using visible or absolute coordinates
219    * @return position of region immediately to left of column
220    */
221   private HiddenCursorPosition searchBackward(HiddenCursorPosition p,
222           int column, boolean useVisible)
223   {
224     int i = p.getRegionIndex();
225     int h = p.getHiddenSoFar();
226
227     // used to add in hiddenColumns offset when working with visible columns
228     int offset = (useVisible ? 1 : 0);
229
230     while ((i > 0) && (hiddenColumns.get(i - 1)[1] >= column + offset * h))
231     {
232       i--;
233       int[] region = hiddenColumns.get(i);
234       h -= region[1] - region[0] + 1;
235     }
236     return new HiddenCursorPosition(i, h);
237   }
238
239 }