4eb8c9508ae9bb9547c6b52219317c8eaee7be5a
[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 = Math.max(getVisibleAlignmentWidth() - 1, 0);
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 if (end > getVisibleAlignmentWidth() - 1)
143     {
144       endRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
145     }
146     else
147     {
148       endRes = end;
149     }
150
151     changeSupport.firePropertyChange("startres", oldstartres, startRes);
152     if (oldstartres == startRes)
153     {
154       // event won't be fired if start positions are same
155       // fire an event for the end positions in case they changed
156       changeSupport.firePropertyChange("endres", oldendres, endRes);
157     }
158   }
159
160   /**
161    * Set last residue visible in the viewport. Fires a property change event.
162    * 
163    * @param res
164    *          residue position
165    */
166   public void setEndRes(int res)
167   {
168     int startres = res;
169     int width = getViewportWidth();
170     if (startres + width - 1 > getVisibleAlignmentWidth() - 1)
171     {
172       startres = getVisibleAlignmentWidth() - width;
173     }
174     setStartEndRes(startres - width + 1, startres);
175   }
176
177   /**
178    * Set the first sequence visible in the viewport, maintaining the height. If
179    * the viewport would extend past the last sequence, sets the viewport so it
180    * sits at the bottom of the alignment. Fires a property change event.
181    * 
182    * @param seq
183    *          sequence position
184    */
185   public void setStartSeq(int seq)
186   {
187     int startseq = seq;
188     int height = getViewportHeight();
189     if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
190     {
191       startseq = getVisibleAlignmentHeight() - height;
192     }
193     setStartEndSeq(startseq, startseq + height - 1);
194   }
195
196   /**
197    * Set start and end sequences at the same time. The viewport height may
198    * change. This method only fires one event for the two changes, and should be
199    * used in preference to separate calls to setStartSeq and setEndSeq.
200    * 
201    * @param start
202    *          the start sequence
203    * @param end
204    *          the end sequence
205    */
206   public void setStartEndSeq(int start, int end)
207   {
208     int oldstartseq = this.startSeq;
209     if (start > getVisibleAlignmentHeight() - 1)
210     {
211       startSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
212     }
213     else if (start < 0)
214     {
215       startSeq = 0;
216     }
217     else
218     {
219       startSeq = start;
220     }
221
222     int oldendseq = this.endSeq;
223     if (end >= getVisibleAlignmentHeight())
224     {
225       endSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
226     }
227     else if (end < 0)
228     {
229       endSeq = 0;
230     }
231     else
232     {
233       endSeq = end;
234     }
235
236     changeSupport.firePropertyChange("startseq", oldstartseq, startSeq);
237     if (oldstartseq == startSeq)
238     {
239       // event won't be fired if start positions are the same
240       // fire in case the end positions changed
241       changeSupport.firePropertyChange("endseq", oldendseq, endSeq);
242     }
243   }
244
245   /**
246    * Set the last sequence visible in the viewport. Fires a property change
247    * event.
248    * 
249    * @param seq
250    *          sequence position
251    */
252   public void setEndSeq(int seq)
253   {
254     int height = getViewportHeight();
255     setStartEndSeq(seq - height + 1, seq);
256   }
257
258   /**
259    * Get start residue of viewport
260    */
261   public int getStartRes()
262   {
263     return startRes;
264   }
265
266   /**
267    * Get end residue of viewport
268    */
269   public int getEndRes()
270   {
271     return endRes;
272   }
273
274   /**
275    * Get start sequence of viewport
276    */
277   public int getStartSeq()
278   {
279     return startSeq;
280   }
281
282   /**
283    * Get end sequence of viewport
284    */
285   public int getEndSeq()
286   {
287     return endSeq;
288   }
289
290   /**
291    * Set viewport width in residues, without changing startRes. Use in
292    * preference to calculating endRes from the width, to avoid out by one
293    * errors! Fires a property change event.
294    * 
295    * @param w
296    *          width in residues
297    */
298   public void setViewportWidth(int w)
299   {
300     setStartEndRes(startRes, startRes + w - 1);
301   }
302
303   /**
304    * Set viewport height in residues, without changing startSeq. Use in
305    * preference to calculating endSeq from the height, to avoid out by one
306    * errors! Fires a property change event.
307    * 
308    * @param h
309    *          height in sequences
310    */
311   public void setViewportHeight(int h)
312   {
313     setStartEndSeq(startSeq, startSeq + h - 1);
314   }
315
316   /**
317    * Set viewport horizontal start position and width. Use in preference to
318    * calculating endRes from the width, to avoid out by one errors! Fires a
319    * property change event.
320    * 
321    * @param start
322    *          start residue
323    * @param w
324    *          width in residues
325    */
326   public void setViewportStartAndWidth(int start, int w)
327   {
328     int vpstart = start;
329     if (vpstart < 0)
330     {
331       vpstart = 0;
332     }
333     else if ((w <= getVisibleAlignmentWidth())
334             && (vpstart + w - 1 > getVisibleAlignmentWidth() - 1))
335     // viewport width is less than the full alignment and we are running off the
336     // RHS edge
337     {
338       vpstart = getVisibleAlignmentWidth() - w;
339     }
340     setStartEndRes(vpstart, vpstart + w - 1);
341   }
342
343   /**
344    * Set viewport vertical start position and height. Use in preference to
345    * calculating endSeq from the height, to avoid out by one errors! Fires a
346    * property change event.
347    * 
348    * @param start
349    *          start sequence
350    * @param h
351    *          height in sequences
352    */
353   public void setViewportStartAndHeight(int start, int h)
354   {
355     int vpstart = start;
356     if (vpstart < 0)
357     {
358       vpstart = 0;
359     }
360     else if ((h <= getVisibleAlignmentHeight())
361             && (vpstart + h - 1 > getVisibleAlignmentHeight() - 1))
362     // viewport height is less than the full alignment and we are running off
363     // the bottom
364     {
365       vpstart = getVisibleAlignmentHeight() - h;
366     }
367     setStartEndSeq(vpstart, vpstart + h - 1);
368   }
369
370   /**
371    * Get width of viewport in residues
372    * 
373    * @return width of viewport
374    */
375   public int getViewportWidth()
376   {
377     return (endRes - startRes + 1);
378   }
379
380   /**
381    * Get height of viewport in residues
382    * 
383    * @return height of viewport
384    */
385   public int getViewportHeight()
386   {
387     return (endSeq - startSeq + 1);
388   }
389
390   /**
391    * Scroll the viewport range vertically. Fires a property change event.
392    * 
393    * @param up
394    *          true if scrolling up, false if down
395    * 
396    * @return true if the scroll is valid
397    */
398   public boolean scrollUp(boolean up)
399   {
400     if (up)
401     {
402       if (startSeq < 1)
403       {
404         return false;
405       }
406
407       setStartSeq(startSeq - 1);
408     }
409     else
410     {
411       if (endSeq >= getVisibleAlignmentHeight() - 1)
412       {
413         return false;
414       }
415
416       setStartSeq(startSeq + 1);
417     }
418     return true;
419   }
420
421   /**
422    * Scroll the viewport range horizontally. Fires a property change event.
423    * 
424    * @param right
425    *          true if scrolling right, false if left
426    * 
427    * @return true if the scroll is valid
428    */
429   public boolean scrollRight(boolean right)
430   {
431     if (!right)
432     {
433       if (startRes < 1)
434       {
435         return false;
436       }
437
438       setStartRes(startRes - 1);
439     }
440     else
441     {
442       if (endRes >= getVisibleAlignmentWidth() - 1)
443       {
444         return false;
445       }
446
447       setStartRes(startRes + 1);
448     }
449
450     return true;
451   }
452
453   /**
454    * Scroll a wrapped alignment so that the specified residue is visible. Fires
455    * a property change event.
456    * 
457    * @param res
458    *          residue position to scroll to
459    */
460   public void scrollToWrappedVisible(int res)
461   {
462     // get the start residue of the wrapped row which res is in
463     // and set that as our start residue
464     int width = getViewportWidth();
465     setStartRes((res / width) * width);
466   }
467
468   /**
469    * Scroll so that (x,y) is visible. Fires a property change event.
470    * 
471    * @param x
472    *          x position in alignment
473    * @param y
474    *          y position in alignment
475    */
476   public void scrollToVisible(int x, int y)
477   {
478     while (y < startSeq)
479     {
480       scrollUp(true);
481     }
482     while (y > endSeq)
483     {
484       scrollUp(false);
485     }
486
487     HiddenColumns hidden = al.getHiddenColumns();
488     while (x < hidden.adjustForHiddenColumns(startRes))
489     {
490       if (!scrollRight(false))
491       {
492         break;
493       }
494     }
495     while (x > hidden.adjustForHiddenColumns(endRes))
496     {
497       if (!scrollRight(true))
498       {
499         break;
500       }
501     }
502   }
503   
504   /**
505    * Adjust sequence position for page up. Fires a property change event.
506    */
507   public void pageUp()
508   {
509     setViewportStartAndHeight(2 * startSeq - endSeq, getViewportHeight());
510   }
511   
512   /**
513    * Adjust sequence position for page down. Fires a property change event.
514    */
515   public void pageDown()
516   {
517     setViewportStartAndHeight(endSeq, getViewportHeight());
518   }
519 }