2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.viewmodel;
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.HiddenColumns;
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
32 public class ViewportRanges extends ViewportProperties
34 public static final String STARTRES = "startres";
36 public static final String ENDRES = "endres";
38 public static final String STARTSEQ = "startseq";
40 public static final String ENDSEQ = "endseq";
42 public static final String STARTRESANDSEQ = "startresandseq";
44 public static final String MOVE_VIEWPORT = "move_viewport";
46 private boolean wrappedMode = false;
48 // start residue of viewport
51 // end residue of viewport
54 // start sequence of viewport
57 // end sequence of viewport
61 private AlignmentI al;
67 * the viewport's alignment
69 public ViewportRanges(AlignmentI alignment)
71 // initial values of viewport settings
73 this.endRes = alignment.getWidth() - 1;
75 this.endSeq = alignment.getHeight() - 1;
80 * Get alignment width in cols, including hidden cols
82 public int getAbsoluteAlignmentWidth()
88 * Get alignment height in rows, including hidden rows
90 public int getAbsoluteAlignmentHeight()
92 return al.getHeight() + al.getHiddenSequences().getSize();
96 * Get alignment width in cols, excluding hidden cols
98 public int getVisibleAlignmentWidth()
100 return al.getVisibleWidth();
104 * Get alignment height in rows, excluding hidden rows
106 public int getVisibleAlignmentHeight()
108 return al.getHeight();
112 * Set first residue visible in the viewport, and retain the current width.
113 * Fires a property change event.
118 public void setStartRes(int res)
120 int width = getViewportWidth();
121 setStartEndRes(res, res + width - 1);
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.
134 public void setStartEndRes(int start, int end)
136 int[] oldvalues = updateStartEndRes(start, end);
137 int oldstartres = oldvalues[0];
138 int oldendres = oldvalues[1];
140 if (oldstartres == startRes && oldendres == endRes)
142 return; // BH 2019.07.27 standard check for no changes
145 // "STARTRES" is a misnomer here -- really "STARTORENDRES"
146 // note that this could be "no change" if the range is just being expanded
147 changeSupport.firePropertyChange(STARTRES, oldstartres, startRes);
148 if (oldstartres == startRes)
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 changeSupport.firePropertyChange(ENDRES, oldendres, endRes);
162 * Update start and end residue values, adjusting for width constraints if
169 * @return array containing old start and end residue values
171 private int[] updateStartEndRes(int start, int end)
173 int oldstartres = this.startRes;
176 * if not wrapped, don't leave white space at the right margin
178 int lastColumn = getVisibleAlignmentWidth() - 1;
179 if (!wrappedMode && (start > lastColumn))
181 startRes = Math.max(lastColumn, 0);
192 int oldendres = this.endRes;
197 else if (!wrappedMode && (end > lastColumn))
199 endRes = Math.max(lastColumn, 0);
205 return new int[] { oldstartres, oldendres };
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.
216 public void setStartSeq(int seq)
219 int height = getViewportHeight();
220 if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
222 startseq = getVisibleAlignmentHeight() - height;
224 setStartEndSeq(startseq, startseq + height - 1);
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.
237 public void setStartEndSeq(int start, int end)
239 int[] oldvalues = updateStartEndSeq(start, end);
240 int oldstartseq = oldvalues[0];
241 int oldendseq = oldvalues[1];
243 changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq);
244 if (oldstartseq == startSeq)
246 // event won't be fired if start positions are the same
247 // fire in case the end positions changed
248 changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq);
253 * Update start and end sequence values, adjusting for height constraints if
260 * @return array containing old start and end sequence values
262 private int[] updateStartEndSeq(int start, int end)
265 // if (end == 3 && this.endSeq == 14 || end == 13 && this.endSeq == 3) {
266 // new NullPointerException().printStackTrace(System.out);
267 // System.out.println("ViewportRange updateStartEndSeq " + start + " " + end + " " + Thread.currentThread());
269 int oldstartseq = this.startSeq;
270 int oldendseq = this.endSeq;
271 int max = getVisibleAlignmentHeight() - 1;
272 startSeq = Math.max(0, Math.min(start, max));
273 endSeq = Math.max(0, Math.min(end, max));
274 return new int[] { oldstartseq, oldendseq };
278 * Set the last sequence visible in the viewport. Fires a property change
282 * sequence position in the range [0, height)
284 public void setEndSeq(int seq)
286 // BH 2018.04.18 added safety for seq < 0; comment about not being >= height
287 setStartEndSeq(Math.max(0, seq + 1 - getViewportHeight()), seq);
291 * Set start residue and start sequence together (fires single event). The
292 * event supplies a pair of old values and a pair of new values: [old start
293 * residue, old start sequence] and [new start residue, new start sequence]
300 public void setStartResAndSeq(int res, int seq)
302 int width = getViewportWidth();
303 int[] oldresvalues = updateStartEndRes(res, res + width - 1);
306 int height = getViewportHeight();
307 if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
309 startseq = getVisibleAlignmentHeight() - height;
311 int[] oldseqvalues = updateStartEndSeq(startseq, startseq + height - 1);
313 int[] old = new int[] { oldresvalues[0], oldseqvalues[0] };
314 int[] newresseq = new int[] { startRes, startSeq };
315 changeSupport.firePropertyChange(STARTRESANDSEQ, old, newresseq);
319 * Get start residue of viewport
321 public int getStartRes()
327 * Get end residue of viewport
329 public int getEndRes()
335 * Get start sequence of viewport
337 public int getStartSeq()
343 * Get end sequence of viewport
345 public int getEndSeq()
351 * Set viewport width in residues, without changing startRes. Use in
352 * preference to calculating endRes from the width, to avoid out by one
353 * errors! Fires a property change event.
358 public void setViewportWidth(int w)
360 setStartEndRes(startRes, startRes + w - 1);
364 * Set viewport height in residues, without changing startSeq. Use in
365 * preference to calculating endSeq from the height, to avoid out by one
366 * errors! Fires a property change event.
369 * height in sequences
371 public void setViewportHeight(int h)
373 setStartEndSeq(startSeq, startSeq + h - 1);
377 * Set viewport horizontal start position and width. Use in preference to
378 * calculating endRes from the width, to avoid out by one errors! Fires a
379 * property change event.
386 public void setViewportStartAndWidth(int start, int w)
388 int vpstart = Math.max(0, start);
392 // if not wrapped, don't leave white space at the right margin
393 int maxStart = getVisibleAlignmentWidth() - w;
396 vpstart = Math.min(vpstart, maxStart);
400 setStartEndRes(vpstart, vpstart + w - 1);
404 * Set viewport vertical start position and height. Use in preference to
405 * calculating endSeq from the height, to avoid out by one errors! Fires a
406 * property change event.
411 * height in sequences
413 public void setViewportStartAndHeight(int start, int h)
415 int vpstart = Math.max(0, start);
416 int maxStart = getVisibleAlignmentHeight() - h;
419 // can't start higher than vertical extent will allow
420 // (viewport height is less than the full alignment
421 // and we are running off the bottom)
422 vpstart = Math.min(vpstart, maxStart);
424 setStartEndSeq(vpstart, vpstart + h - 1);
428 * Get width of viewport in residues
430 * @return width of viewport
432 public int getViewportWidth()
434 return (endRes - startRes + 1);
438 * Get height of viewport in residues
440 * @return height of viewport
442 public int getViewportHeight()
444 return (endSeq - startSeq + 1);
448 * Scroll the viewport range vertically. Fires a property change event.
451 * true if scrolling up, false if down
453 * @return true if the scroll is valid
455 public boolean scrollUp(boolean up)
458 * if in unwrapped mode, scroll up or down one sequence row;
459 * if in wrapped mode, scroll by one visible width of columns
473 setStartSeq(startSeq - 1);
484 if (endSeq >= getVisibleAlignmentHeight() - 1)
488 setStartSeq(startSeq + 1);
495 * Scroll the viewport range horizontally. Fires a property change event.
498 * true if scrolling right, false if left
500 * @return true if the scroll is valid
502 public boolean scrollRight(boolean right)
511 setStartRes(startRes - 1);
515 if (endRes >= getVisibleAlignmentWidth() - 1)
520 setStartRes(startRes + 1);
527 * Scroll a wrapped alignment so that the specified residue is in the first
528 * repeat of the wrapped view. Fires a property change event. Answers true if
529 * the startRes changed, else false.
532 * residue position to scroll to NB visible position not absolute
536 public boolean scrollToWrappedVisible(int res)
538 int newStartRes = calcWrappedStartResidue(res);
539 if (newStartRes == startRes)
543 setStartRes(newStartRes);
549 * Calculate wrapped start residue from visible start residue
552 * visible start residue
553 * @return left column of panel res will be located in
555 private int calcWrappedStartResidue(int res)
557 int oldStartRes = startRes;
558 int width = getViewportWidth();
560 boolean up = res < oldStartRes;
561 int widthsToScroll = Math.abs((res - oldStartRes) / width);
567 int residuesToScroll = width * widthsToScroll;
568 int newStartRes = up ? oldStartRes - residuesToScroll
569 : oldStartRes + residuesToScroll;
578 * Scroll so that (x,y) is visible. Fires a property change event.
581 * x position in alignment (absolute position)
583 * y position in alignment (absolute position)
585 public void scrollToVisible(int x, int y)
596 HiddenColumns hidden = al.getHiddenColumns();
597 while (x < hidden.visibleToAbsoluteColumn(startRes))
599 if (!scrollRight(false))
604 while (x > hidden.visibleToAbsoluteColumn(endRes))
606 if (!scrollRight(true))
614 * Set the viewport location so that a position is visible
617 * column to be visible: absolute position in alignment
619 * row to be visible: absolute position in alignment
621 public boolean setViewportLocation(int x, int y)
623 boolean changedLocation = false;
625 // convert the x,y location to visible coordinates
626 int visX = al.getHiddenColumns().absoluteToVisibleColumn(x);
627 int visY = al.getHiddenSequences().findIndexWithoutHiddenSeqs(y);
629 // if (vis_x,vis_y) is already visible don't do anything
630 if (startRes > visX || visX > endRes
631 || startSeq > visY && visY > endSeq)
633 int[] old = new int[] { startRes, startSeq };
637 int newstartres = calcWrappedStartResidue(visX);
638 setStartRes(newstartres);
639 newresseq = new int[] { startRes, startSeq };
643 // set the viewport x location to contain vis_x
644 int newstartres = visX;
645 int width = getViewportWidth();
646 if (newstartres + width - 1 > getVisibleAlignmentWidth() - 1)
648 newstartres = getVisibleAlignmentWidth() - width;
650 updateStartEndRes(newstartres, newstartres + width - 1);
652 // set the viewport y location to contain vis_y
653 int newstartseq = visY;
654 int height = getViewportHeight();
655 if (newstartseq + height - 1 > getVisibleAlignmentHeight() - 1)
657 newstartseq = getVisibleAlignmentHeight() - height;
659 updateStartEndSeq(newstartseq, newstartseq + height - 1);
661 newresseq = new int[] { startRes, startSeq };
663 changedLocation = true;
664 changeSupport.firePropertyChange(MOVE_VIEWPORT, old, newresseq);
666 return changedLocation;
670 * Adjust sequence position for page up. Fires a property change event.
676 setStartRes(Math.max(0, getStartRes() - getViewportWidth()));
680 setViewportStartAndHeight(startSeq - (endSeq - startSeq),
681 getViewportHeight());
686 * Adjust sequence position for page down. Fires a property change event.
688 public void pageDown()
693 * if height is more than width (i.e. not all sequences fit on screen),
694 * increase page down to height
696 int newStart = getStartRes()
697 + Math.max(getViewportHeight(), getViewportWidth());
700 * don't page down beyond end of alignment, or if not all
701 * sequences fit in the visible height
703 if (newStart < getVisibleAlignmentWidth())
705 setStartRes(newStart);
710 setViewportStartAndHeight(endSeq, getViewportHeight());
714 public void setWrappedMode(boolean wrapped)
716 wrappedMode = wrapped;
719 public boolean isWrappedMode()
725 * Answers the vertical scroll position (0..) to set, given the visible column
726 * that is at top left.
730 * viewport width 40 columns (0-39, 40-79, 80-119...)
731 * column 0 returns scroll position 0
732 * columns 1-40 return scroll position 1
733 * columns 41-80 return scroll position 2
737 * @param topLeftColumn
741 public int getWrappedScrollPosition(final int topLeftColumn)
743 int w = getViewportWidth();
746 * visible whole widths
748 int scroll = topLeftColumn / w;
751 * add 1 for a part width if there is one
753 scroll += topLeftColumn % w > 0 ? 1 : 0;
759 * Answers the maximum wrapped vertical scroll value, given the column
760 * position (0..) to show at top left of the visible region.
762 * @param topLeftColumn
765 public int getWrappedMaxScroll(int topLeftColumn)
767 int scrollPosition = getWrappedScrollPosition(topLeftColumn);
770 * how many more widths could be drawn after this one?
772 int columnsRemaining = getVisibleAlignmentWidth() - topLeftColumn;
773 int width = getViewportWidth();
774 int widthsRemaining = columnsRemaining / width
775 + (columnsRemaining % width > 0 ? 1 : 0) - 1;
776 int maxScroll = scrollPosition + widthsRemaining;
783 public String toString() {
784 return "[ViewportRange " + startSeq + "-" + endSeq + ", "+ startRes + "-" + endRes + "]";