36e598f6d24c44fa5de747632c51099e5b1c8e53
[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   public static final String STARTRES = "startres";
36
37   public static final String ENDRES = "endres";
38
39   public static final String STARTSEQ = "startseq";
40
41   public static final String ENDSEQ = "endseq";
42
43   private boolean wrappedMode = false;
44
45   // start residue of viewport
46   private int startRes;
47
48   // end residue of viewport
49   private int endRes;
50
51   // start sequence of viewport
52   private int startSeq;
53
54   // end sequence of viewport
55   private int endSeq;
56
57   // alignment
58   private AlignmentI al;
59
60   /**
61    * Constructor
62    * 
63    * @param alignment
64    *          the viewport's alignment
65    */
66   public ViewportRanges(AlignmentI alignment)
67   {
68     // initial values of viewport settings
69     this.startRes = 0;
70     this.endRes = alignment.getWidth() - 1;
71     this.startSeq = 0;
72     this.endSeq = alignment.getHeight() - 1;
73     this.al = alignment;
74   }
75
76   /**
77    * Get alignment width in cols, including hidden cols
78    */
79   public int getAbsoluteAlignmentWidth()
80   {
81     return al.getWidth();
82   }
83
84   /**
85    * Get alignment height in rows, including hidden rows
86    */
87   public int getAbsoluteAlignmentHeight()
88   {
89     return al.getHeight() + al.getHiddenSequences().getSize();
90   }
91
92   /**
93    * Get alignment width in cols, excluding hidden cols
94    */
95   public int getVisibleAlignmentWidth()
96   {
97     return al.getWidth() - al.getHiddenColumns().getSize();
98   }
99
100   /**
101    * Get alignment height in rows, excluding hidden rows
102    */
103   public int getVisibleAlignmentHeight()
104   {
105     return al.getHeight();
106   }
107
108   /**
109    * Set first residue visible in the viewport, and retain the current width.
110    * Fires a property change event.
111    * 
112    * @param res
113    *          residue position
114    */
115   public void setStartRes(int res)
116   {
117     int width = getViewportWidth();
118     setStartEndRes(res, res + width - 1);
119   }
120
121   /**
122    * Set start and end residues at the same time. This method only fires one
123    * event for the two changes, and should be used in preference to separate
124    * calls to setStartRes and setEndRes.
125    * 
126    * @param start
127    *          the start residue
128    * @param end
129    *          the end residue
130    */
131   public void setStartEndRes(int start, int end)
132   {
133     int oldstartres = this.startRes;
134
135     /*
136      * if not wrapped, don't leave white space at the right margin
137      */
138     if (!wrappedMode && (start > getVisibleAlignmentWidth() - 1))
139     {
140       startRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
141     }
142     else if (start < 0)
143     {
144       startRes = 0;
145     }
146     else
147     {
148       startRes = start;
149     }
150
151     int oldendres = this.endRes;
152     if (end < 0)
153     {
154       endRes = 0;
155     }
156     else if (!wrappedMode && (end > getVisibleAlignmentWidth() - 1))
157     {
158       endRes = Math.max(getVisibleAlignmentWidth() - 1, 0);
159     }
160     else
161     {
162       endRes = end;
163     }
164
165     changeSupport.firePropertyChange(STARTRES, oldstartres, startRes);
166     if (oldstartres == startRes)
167     {
168       // event won't be fired if start positions are same
169       // fire an event for the end positions in case they changed
170       changeSupport.firePropertyChange(ENDRES, oldendres, endRes);
171     }
172   }
173
174   /**
175    * Set last residue visible in the viewport. Fires a property change event.
176    * 
177    * @param res
178    *          residue position
179    */
180   public void setEndRes(int res)
181   {
182     int startres = res;
183     int width = getViewportWidth();
184
185     /*
186      * if not wrapped, don't leave white space at the right margin
187      */
188     if (!wrappedMode)
189     {
190       if (startres + width - 1 > getVisibleAlignmentWidth() - 1)
191       {
192         startres = getVisibleAlignmentWidth() - width;
193       }
194     }
195     setStartEndRes(startres - width + 1, startres);
196   }
197
198   /**
199    * Set the first sequence visible in the viewport, maintaining the height. If
200    * the viewport would extend past the last sequence, sets the viewport so it
201    * sits at the bottom of the alignment. Fires a property change event.
202    * 
203    * @param seq
204    *          sequence position
205    */
206   public void setStartSeq(int seq)
207   {
208     int startseq = seq;
209     int height = getViewportHeight();
210     if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
211     {
212       startseq = getVisibleAlignmentHeight() - height;
213     }
214     setStartEndSeq(startseq, startseq + height - 1);
215   }
216
217   /**
218    * Set start and end sequences at the same time. The viewport height may
219    * change. This method only fires one event for the two changes, and should be
220    * used in preference to separate calls to setStartSeq and setEndSeq.
221    * 
222    * @param start
223    *          the start sequence
224    * @param end
225    *          the end sequence
226    */
227   public void setStartEndSeq(int start, int end)
228   {
229     int oldstartseq = this.startSeq;
230     if (start > getVisibleAlignmentHeight() - 1)
231     {
232       startSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
233     }
234     else if (start < 0)
235     {
236       startSeq = 0;
237     }
238     else
239     {
240       startSeq = start;
241     }
242
243     int oldendseq = this.endSeq;
244     if (end >= getVisibleAlignmentHeight())
245     {
246       endSeq = Math.max(getVisibleAlignmentHeight() - 1, 0);
247     }
248     else if (end < 0)
249     {
250       endSeq = 0;
251     }
252     else
253     {
254       endSeq = end;
255     }
256
257     changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq);
258     if (oldstartseq == startSeq)
259     {
260       // event won't be fired if start positions are the same
261       // fire in case the end positions changed
262       changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq);
263     }
264   }
265
266   /**
267    * Set the last sequence visible in the viewport. Fires a property change
268    * event.
269    * 
270    * @param seq
271    *          sequence position
272    */
273   public void setEndSeq(int seq)
274   {
275     int height = getViewportHeight();
276     setStartEndSeq(seq - height + 1, seq);
277   }
278
279   /**
280    * Get start residue of viewport
281    */
282   public int getStartRes()
283   {
284     return startRes;
285   }
286
287   /**
288    * Get end residue of viewport
289    */
290   public int getEndRes()
291   {
292     return endRes;
293   }
294
295   /**
296    * Get start sequence of viewport
297    */
298   public int getStartSeq()
299   {
300     return startSeq;
301   }
302
303   /**
304    * Get end sequence of viewport
305    */
306   public int getEndSeq()
307   {
308     return endSeq;
309   }
310
311   /**
312    * Set viewport width in residues, without changing startRes. Use in
313    * preference to calculating endRes from the width, to avoid out by one
314    * errors! Fires a property change event.
315    * 
316    * @param w
317    *          width in residues
318    */
319   public void setViewportWidth(int w)
320   {
321     setStartEndRes(startRes, startRes + w - 1);
322   }
323
324   /**
325    * Set viewport height in residues, without changing startSeq. Use in
326    * preference to calculating endSeq from the height, to avoid out by one
327    * errors! Fires a property change event.
328    * 
329    * @param h
330    *          height in sequences
331    */
332   public void setViewportHeight(int h)
333   {
334     setStartEndSeq(startSeq, startSeq + h - 1);
335   }
336
337   /**
338    * Set viewport horizontal start position and width. Use in preference to
339    * calculating endRes from the width, to avoid out by one errors! Fires a
340    * property change event.
341    * 
342    * @param start
343    *          start residue
344    * @param w
345    *          width in residues
346    */
347   public void setViewportStartAndWidth(int start, int w)
348   {
349     int vpstart = start;
350     if (vpstart < 0)
351     {
352       vpstart = 0;
353     }
354
355     /*
356      * if not wrapped, don't leave white space at the right margin
357      */
358     if (!wrappedMode)
359     {
360       if ((w <= getVisibleAlignmentWidth())
361               && (vpstart + w - 1 > getVisibleAlignmentWidth() - 1))
362       {
363         vpstart = getVisibleAlignmentWidth() - w;
364       }
365
366     }
367     setStartEndRes(vpstart, vpstart + w - 1);
368   }
369
370   /**
371    * Set viewport vertical start position and height. Use in preference to
372    * calculating endSeq from the height, to avoid out by one errors! Fires a
373    * property change event.
374    * 
375    * @param start
376    *          start sequence
377    * @param h
378    *          height in sequences
379    */
380   public void setViewportStartAndHeight(int start, int h)
381   {
382     int vpstart = start;
383     if (vpstart < 0)
384     {
385       vpstart = 0;
386     }
387     else if ((h <= getVisibleAlignmentHeight())
388             && (vpstart + h - 1 > getVisibleAlignmentHeight() - 1))
389     // viewport height is less than the full alignment and we are running off
390     // the bottom
391     {
392       vpstart = getVisibleAlignmentHeight() - h;
393     }
394     setStartEndSeq(vpstart, vpstart + h - 1);
395   }
396
397   /**
398    * Get width of viewport in residues
399    * 
400    * @return width of viewport
401    */
402   public int getViewportWidth()
403   {
404     return (endRes - startRes + 1);
405   }
406
407   /**
408    * Get height of viewport in residues
409    * 
410    * @return height of viewport
411    */
412   public int getViewportHeight()
413   {
414     return (endSeq - startSeq + 1);
415   }
416
417   /**
418    * Scroll the viewport range vertically. Fires a property change event.
419    * 
420    * @param up
421    *          true if scrolling up, false if down
422    * 
423    * @return true if the scroll is valid
424    */
425   public boolean scrollUp(boolean up)
426   {
427     if (up)
428     {
429       if (startSeq < 1)
430       {
431         return false;
432       }
433
434       setStartSeq(startSeq - 1);
435     }
436     else
437     {
438       if (endSeq >= getVisibleAlignmentHeight() - 1)
439       {
440         return false;
441       }
442
443       setStartSeq(startSeq + 1);
444     }
445     return true;
446   }
447
448   /**
449    * Scroll the viewport range horizontally. Fires a property change event.
450    * 
451    * @param right
452    *          true if scrolling right, false if left
453    * 
454    * @return true if the scroll is valid
455    */
456   public boolean scrollRight(boolean right)
457   {
458     if (!right)
459     {
460       if (startRes < 1)
461       {
462         return false;
463       }
464
465       setStartRes(startRes - 1);
466     }
467     else
468     {
469       if (endRes >= getVisibleAlignmentWidth() - 1)
470       {
471         return false;
472       }
473
474       setStartRes(startRes + 1);
475     }
476
477     return true;
478   }
479
480   /**
481    * Scroll a wrapped alignment so that the specified residue is visible. Fires
482    * a property change event.
483    * 
484    * @param res
485    *          residue position to scroll to
486    */
487   public void scrollToWrappedVisible(int res)
488   {
489     // get the start residue of the wrapped row which res is in
490     // and set that as our start residue
491     int width = getViewportWidth();
492     setStartRes((res / width) * width);
493   }
494
495   /**
496    * Scroll so that (x,y) is visible. Fires a property change event.
497    * 
498    * @param x
499    *          x position in alignment
500    * @param y
501    *          y position in alignment
502    */
503   public void scrollToVisible(int x, int y)
504   {
505     while (y < startSeq)
506     {
507       scrollUp(true);
508     }
509     while (y > endSeq)
510     {
511       scrollUp(false);
512     }
513
514     HiddenColumns hidden = al.getHiddenColumns();
515     while (x < hidden.adjustForHiddenColumns(startRes))
516     {
517       if (!scrollRight(false))
518       {
519         break;
520       }
521     }
522     while (x > hidden.adjustForHiddenColumns(endRes))
523     {
524       if (!scrollRight(true))
525       {
526         break;
527       }
528     }
529   }
530   
531   /**
532    * Adjust sequence position for page up. Fires a property change event.
533    */
534   public void pageUp()
535   {
536     if (wrappedMode)
537     {
538       setStartRes(Math.max(0, getStartRes() - getViewportWidth()));
539     }
540     else
541     {
542       setViewportStartAndHeight(startSeq - (endSeq - startSeq),
543               getViewportHeight());
544     }
545   }
546   
547   /**
548    * Adjust sequence position for page down. Fires a property change event.
549    */
550   public void pageDown()
551   {
552     if (wrappedMode)
553     {
554       /*
555        * if height is more than width (i.e. not all sequences fit on screen),
556        * increase page down to height
557        */
558       int newStart = getStartRes()
559               + Math.max(getViewportHeight(), getViewportWidth());
560
561       /*
562        * don't page down beyond end of alignment, or if not all
563        * sequences fit in the visible height
564        */
565       if (newStart < getVisibleAlignmentWidth())
566       {
567         setStartRes(newStart);
568       }
569     }
570     else
571     {
572       setViewportStartAndHeight(endSeq, getViewportHeight());
573     }
574   }
575
576   public void setWrappedMode(boolean wrapped)
577   {
578     wrappedMode = wrapped;
579   }
580
581   /**
582    * Answers the vertical scroll position (0..) to set, given the visible column
583    * that is at top left. Note that if called with the total visible width of
584    * the alignment, this gives the maximum cursor scroll value.
585    * 
586    * <pre>
587    * Example:
588    *    viewport width 40 columns (0-39, 40-79, 80-119...)
589    *    column 0 returns scroll position 0
590    *    columns 0-40 return scroll position 1
591    *    columns 41-80 return scroll position 2
592    *    etc
593    * </pre>
594    * 
595    * @param topLeftColumn
596    *          (0..)
597    * @return
598    */
599   public int getWrappedScrollPosition(final int topLeftColumn)
600   {
601     int w = getViewportWidth();
602
603     /*
604      * visible whole widths
605      */
606     int scroll = topLeftColumn / w;
607     scroll += topLeftColumn % w > 0 ? 1 : 0;
608
609     return scroll;
610   }
611 }