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;
26 import java.util.Arrays;
29 * Supplies and updates viewport properties relating to position such as: start
30 * and end residues and sequences; ideally will serve hidden columns/rows too.
31 * Intention also to support calculations for positioning, scrolling etc. such
32 * as finding the middle of the viewport, checking for scrolls off screen
34 public class ViewportRanges extends ViewportProperties
36 public static final String STARTRES = "startres";
38 public static final String ENDRES = "endres";
40 public static final String STARTSEQ = "startseq";
42 public static final String ENDSEQ = "endseq";
44 public static final String STARTRESANDSEQ = "startresandseq";
46 public static final String MOVE_VIEWPORT = "move_viewport";
48 private boolean wrappedMode = false;
50 // start residue of viewport
53 // end residue of viewport
56 // start sequence of viewport
59 // end sequence of viewport
63 private AlignmentI al;
69 * the viewport's alignment
71 public ViewportRanges(AlignmentI alignment)
73 // initial values of viewport settings
75 this.endRes = alignment.getWidth() - 1;
77 this.setEndSeqTest(alignment.getHeight() - 1);
81 public static String sTest = "";
83 private void setEndSeqTest(int val)
90 String st = Thread.currentThread().toString();
91 sTest += "ViewPortRanges.setEndseqTest was " + endSeq + " now " + val
96 sTest += Arrays.toString(new NullPointerException().getStackTrace())
103 * Get alignment width in cols, including hidden cols
105 public int getAbsoluteAlignmentWidth()
107 return al.getWidth();
111 * Get alignment height in rows, including hidden rows
113 public int getAbsoluteAlignmentHeight()
115 return al.getHeight() + al.getHiddenSequences().getSize();
119 * Get alignment width in cols, excluding hidden cols
121 public int getVisibleAlignmentWidth()
123 return al.getVisibleWidth();
127 * Get alignment height in rows, excluding hidden rows
129 public int getVisibleAlignmentHeight()
131 return al.getHeight();
135 * Set first residue visible in the viewport, and retain the current width.
136 * Fires a property change event.
141 public void setStartRes(int res)
143 int width = getViewportWidth();
144 setStartEndRes(res, res + width - 1);
148 * Set start and end residues at the same time. This method only fires one
149 * event for the two changes, and should be used in preference to separate
150 * calls to setStartRes and setEndRes.
157 public void setStartEndRes(int start, int end)
159 int[] oldvalues = updateStartEndRes(start, end);
160 int oldstartres = oldvalues[0];
161 int oldendres = oldvalues[1];
163 if (oldstartres == startRes && oldendres == endRes)
165 return; // BH 2019.07.27 standard check for no changes
168 // "STARTRES" is a misnomer here -- really "STARTORENDRES"
169 // note that this could be "no change" if the range is just being expanded
170 changeSupport.firePropertyChange(STARTRES, oldstartres, startRes);
171 if (oldstartres == startRes)
173 // No listener cares about this
174 // "ENDRES" is a misnomer here -- really "ENDONLYRES"
175 // BH 2019.07.27 adds end change check
176 // fire only if only the end is changed
177 changeSupport.firePropertyChange(ENDRES, oldendres, endRes);
182 * Update start and end residue values, adjusting for width constraints if
189 * @return array containing old start and end residue values
191 private int[] updateStartEndRes(int start, int end)
193 int oldstartres = this.startRes;
196 * if not wrapped, don't leave white space at the right margin
198 int lastColumn = getVisibleAlignmentWidth() - 1;
199 if (!wrappedMode && (start > lastColumn))
201 startRes = Math.max(lastColumn, 0);
212 int oldendres = this.endRes;
217 else if (!wrappedMode && (end > lastColumn))
219 endRes = Math.max(lastColumn, 0);
225 return new int[] { oldstartres, oldendres };
229 * Set the first sequence visible in the viewport, maintaining the height. If
230 * the viewport would extend past the last sequence, sets the viewport so it
231 * sits at the bottom of the alignment. Fires a property change event.
236 public void setStartSeq(int seq)
238 int height = getViewportHeight();
239 int startseq = Math.min(seq, getVisibleAlignmentHeight() - height);
240 // BH 2019.07.27 cosmetic only -- was:
241 // if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
243 // startseq = getVisibleAlignmentHeight() - height;
245 setStartEndSeq(startseq, startseq + height - 1);
249 * Set start and end sequences at the same time. The viewport height may
250 * change. This method only fires one event for the two changes, and should be
251 * used in preference to separate calls to setStartSeq and setEndSeq.
258 public void setStartEndSeq(int start, int end)
260 // System.out.println("ViewportRange setStartEndSeq " + start + " " + end);
261 int[] oldvalues = updateStartEndSeq(start, end);
262 int oldstartseq = oldvalues[0];
263 int oldendseq = oldvalues[1];
265 if (oldstartseq == startSeq && oldendseq == endSeq)
267 return; // BH 2019.07.27 standard check for no changes
270 // "STARTSEQ" is a misnomer here -- really "STARTORENDSEQ"
271 changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq);
272 if (oldstartseq == startSeq)
274 // Note that all listeners ignore this - could be removed, or there is a
276 // "ENDSEQ" is a misnomer here -- really "ENDONLYSEQ"
277 // additional fire, only if only the end is changed
278 changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq);
283 * Update start and end sequence values, adjusting for height constraints if
290 * @return array containing old start and end sequence values
292 private int[] updateStartEndSeq(int start, int end)
294 int oldstartseq = this.startSeq;
295 int visibleHeight = getVisibleAlignmentHeight();
296 if (start > visibleHeight - 1)
298 startSeq = Math.max(visibleHeight - 1, 0);
309 int oldendseq = this.endSeq;
310 if (end >= visibleHeight)
312 setEndSeqTest(Math.max(visibleHeight - 1, 0));
322 return new int[] { oldstartseq, oldendseq };
326 * Set the last sequence visible in the viewport. Fires a property change
330 * sequence position in the range [0, height)
332 public void setEndSeq(int seq)
334 // BH 2018.04.18 added safety for seq < 0; comment about not being >= height
335 setStartEndSeq(Math.max(0, seq + 1 - getViewportHeight()), seq);
339 * Set start residue and start sequence together (fires single event). The
340 * event supplies a pair of old values and a pair of new values: [old start
341 * residue, old start sequence] and [new start residue, new start sequence]
348 public void setStartResAndSeq(int res, int seq)
350 int width = getViewportWidth();
351 int[] oldresvalues = updateStartEndRes(res, res + width - 1);
354 int height = getViewportHeight();
355 if (startseq + height - 1 > getVisibleAlignmentHeight() - 1)
357 startseq = getVisibleAlignmentHeight() - height;
359 int[] oldseqvalues = updateStartEndSeq(startseq, startseq + height - 1);
361 int[] oldvalues = new int[] { oldresvalues[0], oldseqvalues[0] };
362 int[] newvalues = new int[] { startRes, startSeq };
363 changeSupport.firePropertyChange(STARTRESANDSEQ, oldvalues, newvalues);
367 * Get start residue of viewport
369 public int getStartRes()
375 * Get end residue of viewport
377 public int getEndRes()
383 * Get start sequence of viewport
385 public int getStartSeq()
391 * Get end sequence of viewport
393 public int getEndSeq()
399 * Set viewport width in residues, without changing startRes. Use in
400 * preference to calculating endRes from the width, to avoid out by one
401 * errors! Fires a property change event.
406 public void setViewportWidth(int w)
408 setStartEndRes(startRes, startRes + w - 1);
412 * Set viewport height in residues, without changing startSeq. Use in
413 * preference to calculating endSeq from the height, to avoid out by one
414 * errors! Fires a property change event.
417 * height in sequences
419 public void setViewportHeight(int h)
421 setStartEndSeq(startSeq, startSeq + h - 1);
425 * Set viewport horizontal start position and width. Use in preference to
426 * calculating endRes from the width, to avoid out by one errors! Fires a
427 * property change event.
434 public void setViewportStartAndWidth(int start, int w)
443 * if not wrapped, don't leave white space at the right margin
447 if ((w <= getVisibleAlignmentWidth())
448 && (vpstart + w - 1 > getVisibleAlignmentWidth() - 1))
450 vpstart = getVisibleAlignmentWidth() - w;
454 setStartEndRes(vpstart, vpstart + w - 1);
458 * Set viewport vertical start position and height. Use in preference to
459 * calculating endSeq from the height, to avoid out by one errors! Fires a
460 * property change event.
465 * height in sequences
467 public void setViewportStartAndHeight(int start, int h)
471 int visHeight = getVisibleAlignmentHeight();
476 else if (h <= visHeight && vpstart + h > visHeight)
477 // viewport height is less than the full alignment and we are running off
480 vpstart = visHeight - h;
483 setStartEndSeq(vpstart, vpstart + h - 1);
487 * Get width of viewport in residues
489 * @return width of viewport
491 public int getViewportWidth()
493 return (endRes - startRes + 1);
497 * Get height of viewport in residues
499 * @return height of viewport
501 public int getViewportHeight()
503 return (endSeq - startSeq + 1);
507 * Scroll the viewport range vertically. Fires a property change event.
510 * true if scrolling up, false if down
512 * @return true if the scroll is valid
514 public boolean scrollUp(boolean up)
517 * if in unwrapped mode, scroll up or down one sequence row;
518 * if in wrapped mode, scroll by one visible width of columns
532 setStartSeq(startSeq - 1);
543 if (endSeq >= getVisibleAlignmentHeight() - 1)
547 setStartSeq(startSeq + 1);
554 * Scroll the viewport range horizontally. Fires a property change event.
557 * true if scrolling right, false if left
559 * @return true if the scroll is valid
561 public boolean scrollRight(boolean right)
570 setStartRes(startRes - 1);
574 if (endRes >= getVisibleAlignmentWidth() - 1)
579 setStartRes(startRes + 1);
586 * Scroll a wrapped alignment so that the specified residue is in the first
587 * repeat of the wrapped view. Fires a property change event. Answers true if
588 * the startRes changed, else false.
591 * residue position to scroll to NB visible position not absolute
595 public boolean scrollToWrappedVisible(int res)
597 int newStartRes = calcWrappedStartResidue(res);
598 if (newStartRes == startRes)
602 setStartRes(newStartRes);
608 * Calculate wrapped start residue from visible start residue
611 * visible start residue
612 * @return left column of panel res will be located in
614 private int calcWrappedStartResidue(int res)
616 int oldStartRes = startRes;
617 int width = getViewportWidth();
619 boolean up = res < oldStartRes;
620 int widthsToScroll = Math.abs((res - oldStartRes) / width);
626 int residuesToScroll = width * widthsToScroll;
627 int newStartRes = up ? oldStartRes - residuesToScroll : oldStartRes
637 * Scroll so that (x,y) is visible. Fires a property change event.
640 * x position in alignment (absolute position)
642 * y position in alignment (absolute position)
644 public void scrollToVisible(int x, int y)
655 HiddenColumns hidden = al.getHiddenColumns();
656 while (x < hidden.visibleToAbsoluteColumn(startRes))
658 if (!scrollRight(false))
663 while (x > hidden.visibleToAbsoluteColumn(endRes))
665 if (!scrollRight(true))
673 * Set the viewport location so that a position is visible
676 * column to be visible: absolute position in alignment
678 * row to be visible: absolute position in alignment
680 public boolean setViewportLocation(int x, int y)
682 boolean changedLocation = false;
684 // convert the x,y location to visible coordinates
685 int visX = al.getHiddenColumns().absoluteToVisibleColumn(x);
686 int visY = al.getHiddenSequences().findIndexWithoutHiddenSeqs(y);
688 // if (vis_x,vis_y) is already visible don't do anything
689 if (startRes > visX || visX > endRes
690 || startSeq > visY && visY > endSeq)
692 int[] old = new int[] { startRes, startSeq };
696 int newstartres = calcWrappedStartResidue(visX);
697 setStartRes(newstartres);
698 newresseq = new int[] { startRes, startSeq };
702 // set the viewport x location to contain vis_x
703 int newstartres = visX;
704 int width = getViewportWidth();
705 if (newstartres + width - 1 > getVisibleAlignmentWidth() - 1)
707 newstartres = getVisibleAlignmentWidth() - width;
709 updateStartEndRes(newstartres, newstartres + width - 1);
711 // set the viewport y location to contain vis_y
712 int newstartseq = visY;
713 int height = getViewportHeight();
714 if (newstartseq + height - 1 > getVisibleAlignmentHeight() - 1)
716 newstartseq = getVisibleAlignmentHeight() - height;
718 updateStartEndSeq(newstartseq, newstartseq + height - 1);
720 newresseq = new int[] { startRes, startSeq };
722 changedLocation = true;
723 changeSupport.firePropertyChange(MOVE_VIEWPORT, old, newresseq);
725 return changedLocation;
729 * Adjust sequence position for page up. Fires a property change event.
735 setStartRes(Math.max(0, getStartRes() - getViewportWidth()));
739 setViewportStartAndHeight(startSeq - (endSeq - startSeq),
740 getViewportHeight());
745 * Adjust sequence position for page down. Fires a property change event.
747 public void pageDown()
752 * if height is more than width (i.e. not all sequences fit on screen),
753 * increase page down to height
755 int newStart = getStartRes()
756 + Math.max(getViewportHeight(), getViewportWidth());
759 * don't page down beyond end of alignment, or if not all
760 * sequences fit in the visible height
762 if (newStart < getVisibleAlignmentWidth())
764 setStartRes(newStart);
769 setViewportStartAndHeight(endSeq, getViewportHeight());
773 public void setWrappedMode(boolean wrapped)
775 wrappedMode = wrapped;
778 public boolean isWrappedMode()
784 * Answers the vertical scroll position (0..) to set, given the visible column
785 * that is at top left.
789 * viewport width 40 columns (0-39, 40-79, 80-119...)
790 * column 0 returns scroll position 0
791 * columns 1-40 return scroll position 1
792 * columns 41-80 return scroll position 2
796 * @param topLeftColumn
800 public int getWrappedScrollPosition(final int topLeftColumn)
802 int w = getViewportWidth();
805 * visible whole widths
807 int scroll = topLeftColumn / w;
810 * add 1 for a part width if there is one
812 scroll += topLeftColumn % w > 0 ? 1 : 0;
818 * Answers the maximum wrapped vertical scroll value, given the column
819 * position (0..) to show at top left of the visible region.
821 * @param topLeftColumn
824 public int getWrappedMaxScroll(int topLeftColumn)
826 int scrollPosition = getWrappedScrollPosition(topLeftColumn);
829 * how many more widths could be drawn after this one?
831 int columnsRemaining = getVisibleAlignmentWidth() - topLeftColumn;
832 int width = getViewportWidth();
833 int widthsRemaining = columnsRemaining / width
834 + (columnsRemaining % width > 0 ? 1 : 0) - 1;
835 int maxScroll = scrollPosition + widthsRemaining;
841 public String toString()
843 return "[ViewportRange startRes=" + startRes + " endRes=" + endRes
844 + " startSeq=" + startSeq + " endSeq=" + endSeq + "]";