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