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