Clover icon

Coverage Report

  1. Project Clover database Wed Dec 3 2025 17:03:17 GMT
  2. Package jalview.viewmodel

File ViewportRanges.java

 

Coverage histogram

../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

86
180
36
1
804
424
85
0.47
5
36
2.36

Classes

Class Line # Actions
ViewportRanges 32 180 85
0.9867549598.7%
 

Contributing tests

This file is covered by 318 tests. .

Source view

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    * Supplies and updates viewport properties relating to position such as: start
28    * and end residues and sequences; ideally will serve hidden columns/rows too.
29    * Intention also to support calculations for positioning, scrolling etc. such
30    * as finding the middle of the viewport, checking for scrolls off screen
31    */
 
32    public class ViewportRanges extends ViewportProperties
33    {
34    public static final String STARTRES = "startres";
35   
36    public static final String ENDRES = "endres";
37   
38    public static final String STARTSEQ = "startseq";
39   
40    public static final String ENDSEQ = "endseq";
41   
42    public static final String STARTRESANDSEQ = "startresandseq";
43   
44    public static final String MOVE_VIEWPORT = "move_viewport";
45   
46    private boolean wrappedMode = false;
47   
48    // start residue of viewport
49    private int startRes;
50   
51    // end residue of viewport
52    private int endRes;
53   
54    // start sequence of viewport
55    private int startSeq;
56   
57    // end sequence of viewport
58    private int endSeq;
59   
60    // alignment
61    private AlignmentI al;
62   
63    /**
64    * Constructor
65    *
66    * @param alignment
67    * the viewport's alignment
68    */
 
69  673 toggle public ViewportRanges(AlignmentI alignment)
70    {
71    // initial values of viewport settings
72  673 this.startRes = 0;
73  673 this.endRes = alignment.getWidth() - 1;
74  673 this.startSeq = 0;
75  673 this.endSeq = alignment.getHeight() - 1;
76  673 this.al = alignment;
77    }
78   
79    /**
80    * Get alignment width in cols, including hidden cols
81    */
 
82  1047 toggle public int getAbsoluteAlignmentWidth()
83    {
84  1047 return al.getWidth();
85    }
86   
87    /**
88    * Get alignment height in rows, including hidden rows
89    */
 
90  747 toggle public int getAbsoluteAlignmentHeight()
91    {
92  747 return al.getHeight() + al.getHiddenSequences().getSize();
93    }
94   
95    /**
96    * Get alignment width in cols, excluding hidden cols
97    */
 
98  4265 toggle public int getVisibleAlignmentWidth()
99    {
100  4265 return al.getVisibleWidth();
101    }
102   
103    /**
104    * Get alignment height in rows, excluding hidden rows
105    */
 
106  4639 toggle public int getVisibleAlignmentHeight()
107    {
108  4639 return al.getHeight();
109    }
110   
111    /**
112    * Set first residue visible in the viewport, and retain the current width.
113    * Fires a property change event.
114    *
115    * @param res
116    * residue position
117    */
 
118  145 toggle public void setStartRes(int res)
119    {
120  145 int width = getViewportWidth();
121  145 setStartEndRes(res, res + width - 1);
122    }
123   
124    /**
125    * Set start and end residues at the same time. This method only fires one
126    * event for the two changes, and should be used in preference to separate
127    * calls to setStartRes and setEndRes.
128    *
129    * @param start
130    * the start residue
131    * @param end
132    * the end residue
133    */
 
134  1506 toggle public void setStartEndRes(int start, int end)
135    {
136  1506 int[] oldvalues = updateStartEndRes(start, end);
137  1506 int oldstartres = oldvalues[0];
138  1506 int oldendres = oldvalues[1];
139   
140  1506 if (oldstartres == startRes && oldendres == endRes)
141    {
142  1105 return; // BH 2019.07.27 standard check for no changes
143    }
144   
145    // "STARTRES" is a misnomer here -- really "STARTORENDRES"
146    // note that this could be "no change" if the range is just being expanded
147  401 changeSupport.firePropertyChange(STARTRES, oldstartres, startRes);
148  401 if (oldstartres == startRes)
149    {
150    // only caught in ViewportRangesTest
151    // No listener cares about this
152    // "ENDRES" is a misnomer here -- really "ENDONLYRES"
153    // BH 2019.07.27 adds end change check
154    // fire only if only the end is changed
155    // event won't be fired if start positions are same
156    // fire an event for the end positions in case they changed
157  272 changeSupport.firePropertyChange(ENDRES, oldendres, endRes);
158    }
159    }
160   
161    /**
162    * Update start and end residue values, adjusting for width constraints if
163    * necessary
164    *
165    * @param start
166    * start residue
167    * @param end
168    * end residue
169    * @return array containing old start and end residue values
170    */
 
171  1629 toggle private int[] updateStartEndRes(int start, int end)
172    {
173  1629 int oldstartres = this.startRes;
174   
175    /*
176    * if not wrapped, don't leave white space at the right margin
177    */
178  1629 int lastColumn = getVisibleAlignmentWidth() - 1;
179  1629 if (!wrappedMode && (start > lastColumn))
180    {
181  6 startRes = Math.max(lastColumn, 0);
182    }
183  1623 else if (start < 0)
184    {
185  4 startRes = 0;
186    }
187    else
188    {
189  1619 startRes = start;
190    }
191   
192  1629 int oldendres = this.endRes;
193  1629 if (end < 0)
194    {
195  3 endRes = 0;
196    }
197  1626 else if (!wrappedMode && (end > lastColumn))
198    {
199  337 endRes = Math.max(lastColumn, 0);
200    }
201    else
202    {
203  1289 endRes = end;
204    }
205  1629 return new int[] { oldstartres, oldendres };
206    }
207   
208    /**
209    * Set the first sequence visible in the viewport, maintaining the height. If
210    * the viewport would extend past the last sequence, sets the viewport so it
211    * sits at the bottom of the alignment. Fires a property change event.
212    *
213    * @param seq
214    * sequence position
215    */
 
216  743 toggle public void setStartSeq(int seq)
217    {
218  743 int startseq = seq;
219  743 int height = getViewportHeight();
220  743 if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
221    {
222  52 startseq = getVisibleAlignmentHeight() - height;
223    }
224  743 setStartEndSeq(startseq, startseq + height - 1);
225    }
226   
227    /**
228    * Set start and end sequences at the same time. The viewport height may
229    * change. This method only fires one event for the two changes, and should be
230    * used in preference to separate calls to setStartSeq and setEndSeq.
231    *
232    * @param start
233    * the start sequence
234    * @param end
235    * the end sequence
236    */
 
237  2086 toggle public void setStartEndSeq(int start, int end)
238    {
239  2086 int[] oldvalues = updateStartEndSeq(start, end);
240  2086 int oldstartseq = oldvalues[0];
241  2086 int oldendseq = oldvalues[1];
242   
243  2086 changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq);
244  2086 if (oldstartseq == startSeq)
245    {
246    // event won't be fired if start positions are the same
247    // fire in case the end positions changed
248  2003 changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq);
249    }
250    }
251   
252    /**
253    * Update start and end sequence values, adjusting for height constraints if
254    * necessary
255    *
256    * @param start
257    * start sequence
258    * @param end
259    * end sequence
260    * @return array containing old start and end sequence values
261    */
 
262  2209 toggle private int[] updateStartEndSeq(int start, int end)
263    {
264  2209 int oldstartseq = this.startSeq;
265  2209 int visibleHeight = getVisibleAlignmentHeight();
266  2209 if (start > visibleHeight - 1)
267    {
268  3 startSeq = Math.max(visibleHeight - 1, 0);
269    }
270  2206 else if (start < 0)
271    {
272  54 startSeq = 0;
273    }
274    else
275    {
276  2152 startSeq = start;
277    }
278   
279  2209 int oldendseq = this.endSeq;
280  2209 if (end >= visibleHeight)
281    {
282  418 endSeq = Math.max(visibleHeight - 1, 0);
283    }
284  1791 else if (end < 0)
285    {
286  126 endSeq = 0;
287    }
288    else
289    {
290  1665 endSeq = end;
291    }
292  2209 return new int[] { oldstartseq, oldendseq };
293    }
294   
295    /**
296    * Set the last sequence visible in the viewport. Fires a property change
297    * event.
298    *
299    * @param seq
300    * sequence position in the range [0, height)
301    */
 
302  24 toggle public void setEndSeq(int seq)
303    {
304    // BH 2018.04.18 added safety for seq < 0; comment about not being >= height
305  24 setStartEndSeq(Math.max(0, seq + 1 - getViewportHeight()), seq);
306    }
307   
308    /**
309    * Set start residue and start sequence together (fires single event). The
310    * event supplies a pair of old values and a pair of new values: [old start
311    * residue, old start sequence] and [new start residue, new start sequence]
312    *
313    * @param res
314    * the start residue
315    * @param seq
316    * the start sequence
317    */
 
318  119 toggle public void setStartResAndSeq(int res, int seq)
319    {
320  119 int width = getViewportWidth();
321  119 int[] oldresvalues = updateStartEndRes(res, res + width - 1);
322   
323  119 int startseq = seq;
324  119 int height = getViewportHeight();
325  119 if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
326    {
327  1 startseq = getVisibleAlignmentHeight() - height;
328    }
329  119 int[] oldseqvalues = updateStartEndSeq(startseq, startseq + height - 1);
330   
331  119 int[] old = new int[] { oldresvalues[0], oldseqvalues[0] };
332  119 int[] newresseq = new int[] { startRes, startSeq };
333  119 changeSupport.firePropertyChange(STARTRESANDSEQ, old, newresseq);
334    }
335   
336    /**
337    * Get start residue of viewport
338    */
 
339  24367 toggle public int getStartRes()
340    {
341  24367 return startRes;
342    }
343   
344    /**
345    * Get end residue of viewport
346    */
 
347  21838 toggle public int getEndRes()
348    {
349  21838 return endRes;
350    }
351   
352    /**
353    * Get start sequence of viewport
354    */
 
355  10079 toggle public int getStartSeq()
356    {
357  10079 return startSeq;
358    }
359   
360    /**
361    * Get end sequence of viewport
362    */
 
363  6739 toggle public int getEndSeq()
364    {
365  6739 return endSeq;
366    }
367   
368    /**
369    * Set viewport width in residues, without changing startRes. Use in
370    * preference to calculating endRes from the width, to avoid out by one
371    * errors! Fires a property change event.
372    *
373    * @param w
374    * width in residues
375    */
 
376  858 toggle public void setViewportWidth(int w)
377    {
378  858 setStartEndRes(startRes, startRes + w - 1);
379    }
380   
381    /**
382    * Set viewport height in residues, without changing startSeq. Use in
383    * preference to calculating endSeq from the height, to avoid out by one
384    * errors! Fires a property change event.
385    *
386    * @param h
387    * height in sequences
388    */
 
389  832 toggle public void setViewportHeight(int h)
390    {
391  832 setStartEndSeq(startSeq, startSeq + h - 1);
392    }
393   
394    /**
395    * Set viewport horizontal start position and width. Use in preference to
396    * calculating endRes from the width, to avoid out by one errors! Fires a
397    * property change event.
398    *
399    * @param start
400    * start residue
401    * @param w
402    * width in residues
403    */
 
404  484 toggle public void setViewportStartAndWidth(int start, int w)
405    {
406  484 int vpstart = Math.max(0, start);
407   
408  484 if (!wrappedMode)
409    {
410    // if not wrapped, don't leave white space at the right margin
411  278 int maxStart = getVisibleAlignmentWidth() - w;
412  278 if (maxStart >= 0)
413    {
414  129 vpstart = Math.min(vpstart, maxStart);
415    }
416   
417    }
418  484 setStartEndRes(vpstart, vpstart + w - 1);
419    }
420   
421    /**
422    * Set viewport vertical start position and height. Use in preference to
423    * calculating endSeq from the height, to avoid out by one errors! Fires a
424    * property change event.
425    *
426    * @param start
427    * start sequence
428    * @param h
429    * height in sequences
430    */
 
431  473 toggle public void setViewportStartAndHeight(int start, int h)
432    {
433  473 int vpstart = Math.max(0, start);
434  473 int maxStart = getVisibleAlignmentHeight() - h;
435  473 if (maxStart > 0)
436    {
437    // can't start higher than vertical extent will allow
438    // (viewport height is less than the full alignment
439    // and we are running off the bottom)
440  276 vpstart = Math.min(vpstart, maxStart);
441    }
442  473 setStartEndSeq(vpstart, vpstart + h - 1);
443    }
444   
445    /**
446    * Get width of viewport in residues
447    *
448    * @return width of viewport
449    */
 
450  5516 toggle public int getViewportWidth()
451    {
452  5516 return (endRes - startRes + 1);
453    }
454   
455    /**
456    * Get height of viewport in residues
457    *
458    * @return height of viewport
459    */
 
460  3298 toggle public int getViewportHeight()
461    {
462  3298 return (endSeq - startSeq + 1);
463    }
464   
465    /**
466    * Scroll the viewport range vertically. Fires a property change event.
467    *
468    * @param up
469    * true if scrolling up, false if down
470    *
471    * @return true if the scroll is valid
472    */
 
473  28 toggle public boolean scrollUp(boolean up)
474    {
475    /*
476    * if in unwrapped mode, scroll up or down one sequence row;
477    * if in wrapped mode, scroll by one visible width of columns
478    */
479  28 if (up)
480    {
481  18 if (wrappedMode)
482    {
483  2 pageUp();
484    }
485    else
486    {
487  16 if (startSeq < 1)
488    {
489  2 return false;
490    }
491  14 setStartSeq(startSeq - 1);
492    }
493    }
494    else
495    {
496  10 if (wrappedMode)
497    {
498  4 pageDown();
499    }
500    else
501    {
502  6 if (endSeq >= getVisibleAlignmentHeight() - 1)
503    {
504  2 return false;
505    }
506  4 setStartSeq(startSeq + 1);
507    }
508    }
509  24 return true;
510    }
511   
512    /**
513    * Scroll the viewport range horizontally. Fires a property change event.
514    *
515    * @param right
516    * true if scrolling right, false if left
517    *
518    * @return true if the scroll is valid
519    */
 
520  24 toggle public boolean scrollRight(boolean right)
521    {
522  24 if (!right)
523    {
524  18 if (startRes < 1)
525    {
526  2 return false;
527    }
528   
529  16 setStartRes(startRes - 1);
530    }
531    else
532    {
533  6 if (endRes >= getVisibleAlignmentWidth() - 1)
534    {
535  2 return false;
536    }
537   
538  4 setStartRes(startRes + 1);
539    }
540   
541  20 return true;
542    }
543   
544    /**
545    * Scroll a wrapped alignment so that the specified residue is in the first
546    * repeat of the wrapped view. Fires a property change event. Answers true if
547    * the startRes changed, else false.
548    *
549    * @param res
550    * residue position to scroll to NB visible position not absolute
551    * alignment position
552    * @return
553    */
 
554  8 toggle public boolean scrollToWrappedVisible(int res)
555    {
556  8 int newStartRes = calcWrappedStartResidue(res);
557  8 if (newStartRes == startRes)
558    {
559  3 return false;
560    }
561  5 setStartRes(newStartRes);
562   
563  5 return true;
564    }
565   
566    /**
567    * Calculate wrapped start residue from visible start residue
568    *
569    * @param res
570    * visible start residue
571    * @return left column of panel res will be located in
572    */
 
573  10 toggle private int calcWrappedStartResidue(int res)
574    {
575  10 int oldStartRes = startRes;
576  10 int width = getViewportWidth();
577   
578  10 boolean up = res < oldStartRes;
579  10 int widthsToScroll = Math.abs((res - oldStartRes) / width);
580  10 if (up)
581    {
582  2 widthsToScroll++;
583    }
584   
585  10 int residuesToScroll = width * widthsToScroll;
586  10 int newStartRes = up ? oldStartRes - residuesToScroll
587    : oldStartRes + residuesToScroll;
588  10 if (newStartRes < 0)
589    {
590  2 newStartRes = 0;
591    }
592  10 return newStartRes;
593    }
594   
595    /**
596    * Scroll so that (x,y) is visible. Fires a property change event.
597    *
598    * @param x
599    * x position in alignment (absolute position)
600    * @param y
601    * y position in alignment (absolute position)
602    */
 
603  5 toggle public void scrollToVisible(int x, int y)
604    {
605  16 while (y < startSeq)
606    {
607  11 scrollUp(true);
608    }
609  6 while (y > endSeq)
610    {
611  1 scrollUp(false);
612    }
613   
614  5 HiddenColumns hidden = al.getHiddenColumns();
615  18 while (x < hidden.visibleToAbsoluteColumn(startRes))
616    {
617  13 if (!scrollRight(false))
618    {
619  0 break;
620    }
621    }
622  6 while (x > hidden.visibleToAbsoluteColumn(endRes))
623    {
624  1 if (!scrollRight(true))
625    {
626  0 break;
627    }
628    }
629    }
630   
631    /**
632    * Set the viewport location so that a position is visible
633    *
634    * @param x
635    * column to be visible: absolute position in alignment
636    * @param y
637    * row to be visible: absolute position in alignment
638    */
 
639  7 toggle public boolean setViewportLocation(int x, int y)
640    {
641  7 boolean changedLocation = false;
642   
643    // convert the x,y location to visible coordinates
644  7 int visX = al.getHiddenColumns().absoluteToVisibleColumn(x);
645  7 int visY = al.getHiddenSequences().findIndexWithoutHiddenSeqs(y);
646   
647    // if (vis_x,vis_y) is already visible don't do anything
648  7 if (startRes > visX || visX > endRes
649    || startSeq > visY && visY > endSeq)
650    {
651  6 int[] old = new int[] { startRes, startSeq };
652  6 int[] newresseq;
653  6 if (wrappedMode)
654    {
655  2 int newstartres = calcWrappedStartResidue(visX);
656  2 setStartRes(newstartres);
657  2 newresseq = new int[] { startRes, startSeq };
658    }
659    else
660    {
661    // set the viewport x location to contain vis_x
662  4 int newstartres = visX;
663  4 int width = getViewportWidth();
664  4 if (newstartres + width - 1 > getVisibleAlignmentWidth() - 1)
665    {
666  2 newstartres = getVisibleAlignmentWidth() - width;
667    }
668  4 updateStartEndRes(newstartres, newstartres + width - 1);
669   
670    // set the viewport y location to contain vis_y
671  4 int newstartseq = visY;
672  4 int height = getViewportHeight();
673  4 if (newstartseq + height - 1 > getVisibleAlignmentHeight() - 1)
674    {
675  1 newstartseq = getVisibleAlignmentHeight() - height;
676    }
677  4 updateStartEndSeq(newstartseq, newstartseq + height - 1);
678   
679  4 newresseq = new int[] { startRes, startSeq };
680    }
681  6 changedLocation = true;
682  6 changeSupport.firePropertyChange(MOVE_VIEWPORT, old, newresseq);
683    }
684  7 return changedLocation;
685    }
686   
687    /**
688    * Adjust sequence position for page up. Fires a property change event.
689    */
 
690  12 toggle public void pageUp()
691    {
692  12 if (wrappedMode)
693    {
694  8 setStartRes(Math.max(0, getStartRes() - getViewportWidth()));
695    }
696    else
697    {
698  4 setViewportStartAndHeight(startSeq - (endSeq - startSeq),
699    getViewportHeight());
700    }
701    }
702   
703    /**
704    * Adjust sequence position for page down. Fires a property change event.
705    */
 
706  17 toggle public void pageDown()
707    {
708  17 if (wrappedMode)
709    {
710    /*
711    * if height is more than width (i.e. not all sequences fit on screen),
712    * increase page down to height
713    */
714  10 int newStart = getStartRes()
715    + Math.max(getViewportHeight(), getViewportWidth());
716   
717    /*
718    * don't page down beyond end of alignment, or if not all
719    * sequences fit in the visible height
720    */
721  10 if (newStart < getVisibleAlignmentWidth())
722    {
723  7 setStartRes(newStart);
724    }
725    }
726    else
727    {
728  7 setViewportStartAndHeight(endSeq, getViewportHeight());
729    }
730    }
731   
 
732  120 toggle public void setWrappedMode(boolean wrapped)
733    {
734  120 wrappedMode = wrapped;
735    }
736   
 
737  116 toggle public boolean isWrappedMode()
738    {
739  116 return wrappedMode;
740    }
741   
742    /**
743    * Answers the vertical scroll position (0..) to set, given the visible column
744    * that is at top left.
745    *
746    * <pre>
747    * Example:
748    * viewport width 40 columns (0-39, 40-79, 80-119...)
749    * column 0 returns scroll position 0
750    * columns 1-40 return scroll position 1
751    * columns 41-80 return scroll position 2
752    * etc
753    * </pre>
754    *
755    * @param topLeftColumn
756    * (0..)
757    * @return
758    */
 
759  424 toggle public int getWrappedScrollPosition(final int topLeftColumn)
760    {
761  424 int w = getViewportWidth();
762   
763    /*
764    * visible whole widths
765    */
766  424 int scroll = topLeftColumn / w;
767   
768    /*
769    * add 1 for a part width if there is one
770    */
771  424 scroll += topLeftColumn % w > 0 ? 1 : 0;
772   
773  424 return scroll;
774    }
775   
776    /**
777    * Answers the maximum wrapped vertical scroll value, given the column
778    * position (0..) to show at top left of the visible region.
779    *
780    * @param topLeftColumn
781    * @return
782    */
 
783  219 toggle public int getWrappedMaxScroll(int topLeftColumn)
784    {
785  219 int scrollPosition = getWrappedScrollPosition(topLeftColumn);
786   
787    /*
788    * how many more widths could be drawn after this one?
789    */
790  219 int columnsRemaining = getVisibleAlignmentWidth() - topLeftColumn;
791  219 int width = getViewportWidth();
792  219 int widthsRemaining = columnsRemaining / width
793  219 + (columnsRemaining % width > 0 ? 1 : 0) - 1;
794  219 int maxScroll = scrollPosition + widthsRemaining;
795   
796  219 return maxScroll;
797    }
798   
799   
 
800  1 toggle @Override
801    public String toString() {
802  1 return "[ViewportRange " + startSeq + "-" + endSeq + ", "+ startRes + "-" + endRes + "]";
803    }
804    }