JAL-1793 spike branch updated to latest
[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     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     /*
406      * if in unwrapped mode, scroll up or down one sequence row;
407      * if in wrapped mode, scroll by one visible width of columns
408      */
409     if (up)
410     {
411       if (wrappedMode)
412       {
413         pageUp();
414       }
415       else
416       {
417         if (startSeq < 1)
418         {
419           return false;
420         }
421         setStartSeq(startSeq - 1);
422       }
423     }
424     else
425     {
426       if (wrappedMode)
427       {
428         pageDown();
429       }
430       else
431       {
432         if (endSeq >= getVisibleAlignmentHeight() - 1)
433         {
434           return false;
435         }
436         setStartSeq(startSeq + 1);
437       }
438     }
439     return true;
440   }
441
442   /**
443    * Scroll the viewport range horizontally. Fires a property change event.
444    * 
445    * @param right
446    *          true if scrolling right, false if left
447    * 
448    * @return true if the scroll is valid
449    */
450   public boolean scrollRight(boolean right)
451   {
452     if (!right)
453     {
454       if (startRes < 1)
455       {
456         return false;
457       }
458
459       setStartRes(startRes - 1);
460     }
461     else
462     {
463       if (endRes >= getVisibleAlignmentWidth() - 1)
464       {
465         return false;
466       }
467
468       setStartRes(startRes + 1);
469     }
470
471     return true;
472   }
473
474   /**
475    * Scroll a wrapped alignment so that the specified residue is in the first
476    * repeat of the wrapped view. Fires a property change event. Answers true if
477    * the startRes changed, else false.
478    * 
479    * @param res
480    *          residue position to scroll to
481    * @return
482    */
483   public boolean scrollToWrappedVisible(int res)
484   {
485     int oldStartRes = startRes;
486     int width = getViewportWidth();
487
488     if (res >= oldStartRes && res < oldStartRes + width)
489     {
490       return false;
491     }
492
493     boolean up = res < oldStartRes;
494     int widthsToScroll = Math.abs((res - oldStartRes) / width);
495     if (up)
496     {
497       widthsToScroll++;
498     }
499
500     int residuesToScroll = width * widthsToScroll;
501     int newStartRes = up ? oldStartRes - residuesToScroll : oldStartRes
502             + residuesToScroll;
503     if (newStartRes < 0)
504     {
505       newStartRes = 0;
506     }
507
508     setStartRes(newStartRes);
509
510     return true;
511   }
512
513   /**
514    * Scroll so that (x,y) is visible. Fires a property change event.
515    * 
516    * @param x
517    *          x position in alignment
518    * @param y
519    *          y position in alignment
520    */
521   public void scrollToVisible(int x, int y)
522   {
523     while (y < startSeq)
524     {
525       scrollUp(true);
526     }
527     while (y > endSeq)
528     {
529       scrollUp(false);
530     }
531
532     HiddenColumns hidden = al.getHiddenColumns();
533     while (x < hidden.adjustForHiddenColumns(startRes))
534     {
535       if (!scrollRight(false))
536       {
537         break;
538       }
539     }
540     while (x > hidden.adjustForHiddenColumns(endRes))
541     {
542       if (!scrollRight(true))
543       {
544         break;
545       }
546     }
547   }
548
549   /**
550    * Adjust sequence position for page up. Fires a property change event.
551    */
552   public void pageUp()
553   {
554     if (wrappedMode)
555     {
556       setStartRes(Math.max(0, getStartRes() - getViewportWidth()));
557     }
558     else
559     {
560       setViewportStartAndHeight(startSeq - (endSeq - startSeq),
561               getViewportHeight());
562     }
563   }
564
565   /**
566    * Adjust sequence position for page down. Fires a property change event.
567    */
568   public void pageDown()
569   {
570     if (wrappedMode)
571     {
572       /*
573        * if height is more than width (i.e. not all sequences fit on screen),
574        * increase page down to height
575        */
576       int newStart = getStartRes()
577               + Math.max(getViewportHeight(), getViewportWidth());
578
579       /*
580        * don't page down beyond end of alignment, or if not all
581        * sequences fit in the visible height
582        */
583       if (newStart < getVisibleAlignmentWidth())
584       {
585         setStartRes(newStart);
586       }
587     }
588     else
589     {
590       setViewportStartAndHeight(endSeq, getViewportHeight());
591     }
592   }
593
594   public void setWrappedMode(boolean wrapped)
595   {
596     wrappedMode = wrapped;
597   }
598
599   public boolean isWrappedMode()
600   {
601     return wrappedMode;
602   }
603
604   /**
605    * Answers the vertical scroll position (0..) to set, given the visible column
606    * that is at top left.
607    * 
608    * <pre>
609    * Example:
610    *    viewport width 40 columns (0-39, 40-79, 80-119...)
611    *    column 0 returns scroll position 0
612    *    columns 1-40 return scroll position 1
613    *    columns 41-80 return scroll position 2
614    *    etc
615    * </pre>
616    * 
617    * @param topLeftColumn
618    *          (0..)
619    * @return
620    */
621   public int getWrappedScrollPosition(final int topLeftColumn)
622   {
623     int w = getViewportWidth();
624
625     /*
626      * visible whole widths
627      */
628     int scroll = topLeftColumn / w;
629
630     /*
631      * add 1 for a part width if there is one
632      */
633     scroll += topLeftColumn % w > 0 ? 1 : 0;
634
635     return scroll;
636   }
637
638   /**
639    * Answers the maximum wrapped vertical scroll value, given the column
640    * position (0..) to show at top left of the visible region.
641    * 
642    * @param topLeftColumn
643    * @return
644    */
645   public int getWrappedMaxScroll(int topLeftColumn)
646   {
647     int scrollPosition = getWrappedScrollPosition(topLeftColumn);
648
649     /*
650      * how many more widths could be drawn after this one?
651      */
652     int columnsRemaining = getVisibleAlignmentWidth() - topLeftColumn;
653     int width = getViewportWidth();
654     int widthsRemaining = columnsRemaining / width
655             + (columnsRemaining % width > 0 ? 1 : 0) - 1;
656     int maxScroll = scrollPosition + widthsRemaining;
657
658     return maxScroll;
659   }
660 }