JAL-2571 Corrected alignment width/height checks to visible only
[jalview.git] / src / jalview / viewmodel / ViewportRanges.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.viewmodel;
22
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
25
26 /**
27  * Slightly less embryonic class which: Supplies and updates viewport properties
28  * relating to position such as: start and end residues and sequences; ideally
29  * will serve hidden columns/rows too. Intention also to support calculations
30  * for positioning, scrolling etc. such as finding the middle of the viewport,
31  * checking for scrolls off screen
32  */
33 public class ViewportRanges extends ViewportProperties
34 {
35   // start residue of viewport
36   private int startRes;
37
38   // end residue of viewport
39   private int endRes;
40
41   // start sequence of viewport
42   private int startSeq;
43
44   // end sequence of viewport
45   private int endSeq;
46
47   // alignment
48   private AlignmentI al;
49
50   /**
51    * Constructor
52    * 
53    * @param alignment
54    *          the viewport's alignment
55    */
56   public ViewportRanges(AlignmentI alignment)
57   {
58     // initial values of viewport settings
59     this.startRes = 0;
60     this.endRes = alignment.getWidth() - 1;
61     this.startSeq = 0;
62     this.endSeq = alignment.getHeight() - 1;
63     this.al = alignment;
64   }
65
66   /**
67    * Get alignment width in cols, including hidden cols
68    */
69   public int getAbsoluteAlignmentWidth()
70   {
71     return al.getWidth();
72   }
73
74   /**
75    * Get alignment height in rows, including hidden rows
76    */
77   public int getAbsoluteAlignmentHeight()
78   {
79     return al.getHeight() + al.getHiddenSequences().getSize();
80   }
81
82   /**
83    * Get alignment width in cols, excluding hidden cols
84    */
85   public int getVisibleAlignmentWidth()
86   {
87     return al.getWidth() - al.getHiddenColumns().getSize();
88   }
89
90   /**
91    * Get alignment height in rows, excluding hidden rows
92    */
93   public int getVisibleAlignmentHeight()
94   {
95     return al.getHeight();
96   }
97
98   /**
99    * Set first residue visible in the viewport, and retain the current width.
100    * Fires a property change event.
101    * 
102    * @param res
103    *          residue position
104    */
105   public void setStartRes(int res)
106   {
107     int width = getViewportWidth();
108     setStartEndRes(res, res + width - 1);
109   }
110
111   /**
112    * Set start and end residues at the same time. This method only fires one
113    * event for the two changes, and should be used in preference to separate
114    * calls to setStartRes and setEndRes.
115    * 
116    * @param start
117    *          the start residue
118    * @param end
119    *          the end residue
120    */
121   public void setStartEndRes(int start, int end)
122   {
123     int oldstartres = this.startRes;
124     if (start > getVisibleAlignmentWidth() - 1)
125     {
126       startRes = getVisibleAlignmentWidth() - 1;
127     }
128     else if (start < 0)
129     {
130       startRes = 0;
131     }
132     else
133     {
134       startRes = start;
135     }
136
137     int oldendres = this.endRes;
138     if (end < 0)
139     {
140       endRes = 0;
141     }
142     else
143     {
144       endRes = end;
145     }
146
147     changeSupport.firePropertyChange("startres", oldstartres, startRes);
148     if (oldstartres == startRes)
149     {
150       // event won't be fired if start positions are same
151       // fire an event for the end positions in case they changed
152       changeSupport.firePropertyChange("endres", oldendres, endRes);
153     }
154   }
155
156   /**
157    * Set last residue visible in the viewport. Fires a property change event.
158    * 
159    * @param res
160    *          residue position
161    */
162   public void setEndRes(int res)
163   {
164     int startres = res;
165     int width = getViewportWidth();
166     if (startres + width - 1 > getVisibleAlignmentWidth() - 1)
167     {
168       startres = getVisibleAlignmentWidth() - width;
169     }
170     setStartEndRes(startres - width + 1, startres);
171   }
172
173   /**
174    * Set the first sequence visible in the viewport, maintaining the height. If
175    * the viewport would extend past the last sequence, sets the viewport so it
176    * sits at the bottom of the alignment. Fires a property change event.
177    * 
178    * @param seq
179    *          sequence position
180    */
181   public void setStartSeq(int seq)
182   {
183     int startseq = seq;
184     int height = getViewportHeight();
185     if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
186     {
187       startseq = getVisibleAlignmentHeight() - height;
188     }
189     setStartEndSeq(startseq, startseq + height - 1);
190   }
191
192   /**
193    * Set start and end sequences at the same time. The viewport height may
194    * change. This method only fires one event for the two changes, and should be
195    * used in preference to separate calls to setStartSeq and setEndSeq.
196    * 
197    * @param start
198    *          the start sequence
199    * @param end
200    *          the end sequence
201    */
202   public void setStartEndSeq(int start, int end)
203   {
204     int oldstartseq = this.startSeq;
205     if (start > getVisibleAlignmentHeight() - 1)
206     {
207       startSeq = getVisibleAlignmentHeight() - 1;
208     }
209     else if (start < 0)
210     {
211       startSeq = 0;
212     }
213     else
214     {
215       startSeq = start;
216     }
217
218     int oldendseq = this.endSeq;
219     if (end >= getVisibleAlignmentHeight())
220     {
221       endSeq = getVisibleAlignmentHeight() - 1;
222     }
223     else if (end < 0)
224     {
225       endSeq = 0;
226     }
227     else
228     {
229       endSeq = end;
230     }
231
232     changeSupport.firePropertyChange("startseq", oldstartseq, startSeq);
233     if (oldstartseq == startSeq)
234     {
235       // event won't be fired if start positions are the same
236       // fire in case the end positions changed
237       changeSupport.firePropertyChange("endseq", oldendseq, endSeq);
238     }
239   }
240
241   /**
242    * Set the last sequence visible in the viewport. Fires a property change
243    * event.
244    * 
245    * @param seq
246    *          sequence position
247    */
248   public void setEndSeq(int seq)
249   {
250     int height = getViewportHeight();
251     setStartEndSeq(seq - height + 1, seq);
252   }
253
254   /**
255    * Get start residue of viewport
256    */
257   public int getStartRes()
258   {
259     return startRes;
260   }
261
262   /**
263    * Get end residue of viewport
264    */
265   public int getEndRes()
266   {
267     return endRes;
268   }
269
270   /**
271    * Get start sequence of viewport
272    */
273   public int getStartSeq()
274   {
275     return startSeq;
276   }
277
278   /**
279    * Get end sequence of viewport
280    */
281   public int getEndSeq()
282   {
283     return endSeq;
284   }
285
286   /**
287    * Set viewport width in residues, without changing startRes. Use in
288    * preference to calculating endRes from the width, to avoid out by one
289    * errors! Fires a property change event.
290    * 
291    * @param w
292    *          width in residues
293    */
294   public void setViewportWidth(int w)
295   {
296     setStartEndRes(startRes, startRes + w - 1);
297   }
298
299   /**
300    * Set viewport height in residues, without changing startSeq. Use in
301    * preference to calculating endSeq from the height, to avoid out by one
302    * errors! Fires a property change event.
303    * 
304    * @param h
305    *          height in sequences
306    */
307   public void setViewportHeight(int h)
308   {
309     setStartEndSeq(startSeq, startSeq + h - 1);
310   }
311
312   /**
313    * Set viewport horizontal start position and width. Use in preference to
314    * calculating endRes from the width, to avoid out by one errors! Fires a
315    * property change event.
316    * 
317    * @param start
318    *          start residue
319    * @param w
320    *          width in residues
321    */
322   public void setViewportStartAndWidth(int start, int w)
323   {
324     int vpstart = start;
325     if (vpstart < 0)
326     {
327       vpstart = 0;
328     }
329     else if (vpstart + w - 1 > getVisibleAlignmentWidth() - 1)
330     {
331       vpstart = getVisibleAlignmentWidth() - 1;
332     }
333     setStartEndRes(vpstart, vpstart + w - 1);
334   }
335
336   /**
337    * Set viewport vertical start position and height. Use in preference to
338    * calculating endSeq from the height, to avoid out by one errors! Fires a
339    * property change event.
340    * 
341    * @param start
342    *          start sequence
343    * @param h
344    *          height in sequences
345    */
346   public void setViewportStartAndHeight(int start, int h)
347   {
348     int vpstart = start;
349     if (vpstart < 0)
350     {
351       vpstart = 0;
352     }
353     else if (vpstart + h - 1 > getVisibleAlignmentHeight() - 1)
354     {
355       vpstart = getVisibleAlignmentHeight() - h;
356     }
357     setStartEndSeq(vpstart, vpstart + h - 1);
358   }
359
360   /**
361    * Get width of viewport in residues
362    * 
363    * @return width of viewport
364    */
365   public int getViewportWidth()
366   {
367     return (endRes - startRes + 1);
368   }
369
370   /**
371    * Get height of viewport in residues
372    * 
373    * @return height of viewport
374    */
375   public int getViewportHeight()
376   {
377     return (endSeq - startSeq + 1);
378   }
379
380   /**
381    * Scroll the viewport range vertically. Fires a property change event.
382    * 
383    * @param up
384    *          true if scrolling up, false if down
385    * 
386    * @return true if the scroll is valid
387    */
388   public boolean scrollUp(boolean up)
389   {
390     if (up)
391     {
392       if (startSeq < 1)
393       {
394         return false;
395       }
396
397       setStartSeq(startSeq - 1);
398     }
399     else
400     {
401       if (endSeq >= getVisibleAlignmentHeight() - 1)
402       {
403         return false;
404       }
405
406       setStartSeq(startSeq + 1);
407     }
408     return true;
409   }
410
411   /**
412    * Scroll the viewport range horizontally. Fires a property change event.
413    * 
414    * @param right
415    *          true if scrolling right, false if left
416    * 
417    * @return true if the scroll is valid
418    */
419   public boolean scrollRight(boolean right)
420   {
421     if (!right)
422     {
423       if (startRes < 1)
424       {
425         return false;
426       }
427
428       setStartRes(startRes - 1);
429     }
430     else
431     {
432       if (endRes > getVisibleAlignmentWidth() - 1)
433       {
434         return false;
435       }
436
437       setStartRes(startRes + 1);
438     }
439
440     return true;
441   }
442
443   /**
444    * Scroll a wrapped alignment so that the specified residue is visible. Fires
445    * a property change event.
446    * 
447    * @param res
448    *          residue position to scroll to
449    */
450   public void scrollToWrappedVisible(int res)
451   {
452     // get the start residue of the wrapped row which res is in
453     // and set that as our start residue
454     int width = getViewportWidth();
455     setStartRes((res / width) * width);
456   }
457
458   /**
459    * Scroll so that (x,y) is visible. Fires a property change event.
460    * 
461    * @param x
462    *          x position in alignment
463    * @param y
464    *          y position in alignment
465    */
466   public void scrollToVisible(int x, int y)
467   {
468     while (y < startSeq)
469     {
470       scrollUp(true);
471     }
472     while (y > endSeq)
473     {
474       scrollUp(false);
475     }
476
477     HiddenColumns hidden = al.getHiddenColumns();
478     while (x < hidden.adjustForHiddenColumns(startRes))
479     {
480       if (!scrollRight(false))
481       {
482         break;
483       }
484     }
485     while (x > hidden.adjustForHiddenColumns(endRes))
486     {
487       if (!scrollRight(true))
488       {
489         break;
490       }
491     }
492   }
493   
494   /**
495    * Adjust sequence position for page up. Fires a property change event.
496    */
497   public void pageUp()
498   {
499     setViewportStartAndHeight(2 * startSeq - endSeq, getViewportHeight());
500   }
501   
502   /**
503    * Adjust sequence position for page down. Fires a property change event.
504    */
505   public void pageDown()
506   {
507     setViewportStartAndHeight(endSeq, getViewportHeight());
508   }
509 }