X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fviewmodel%2FViewportRanges.java;fp=src%2Fjalview%2Fviewmodel%2FViewportRanges.java;h=42d490ea1fcdf2abb3b3d16753a88b4b42012364;hb=f063821ed0be9c1581af74643a1aa5798731af65;hp=0000000000000000000000000000000000000000;hpb=fd18e2c73cd015d4e38ad91da0e5d7532ff0ef42;p=jalview.git diff --git a/src/jalview/viewmodel/ViewportRanges.java b/src/jalview/viewmodel/ViewportRanges.java new file mode 100644 index 0000000..42d490e --- /dev/null +++ b/src/jalview/viewmodel/ViewportRanges.java @@ -0,0 +1,644 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.viewmodel; + +import jalview.datamodel.AlignmentI; +import jalview.datamodel.HiddenColumns; + +/** + * Slightly less embryonic class which: Supplies and updates viewport properties + * relating to position such as: start and end residues and sequences; ideally + * will serve hidden columns/rows too. Intention also to support calculations + * for positioning, scrolling etc. such as finding the middle of the viewport, + * checking for scrolls off screen + */ +public class ViewportRanges extends ViewportProperties +{ + public static final String STARTRES = "startres"; + + public static final String ENDRES = "endres"; + + public static final String STARTSEQ = "startseq"; + + public static final String ENDSEQ = "endseq"; + + private boolean wrappedMode = false; + + // start residue of viewport + private int startRes; + + // end residue of viewport + private int endRes; + + // start sequence of viewport + private int startSeq; + + // end sequence of viewport + private int endSeq; + + // alignment + private AlignmentI al; + + /** + * Constructor + * + * @param alignment + * the viewport's alignment + */ + public ViewportRanges(AlignmentI alignment) + { + // initial values of viewport settings + this.startRes = 0; + this.endRes = alignment.getWidth() - 1; + this.startSeq = 0; + this.endSeq = alignment.getHeight() - 1; + this.al = alignment; + } + + /** + * Get alignment width in cols, including hidden cols + */ + public int getAbsoluteAlignmentWidth() + { + return al.getWidth(); + } + + /** + * Get alignment height in rows, including hidden rows + */ + public int getAbsoluteAlignmentHeight() + { + return al.getHeight() + al.getHiddenSequences().getSize(); + } + + /** + * Get alignment width in cols, excluding hidden cols + */ + public int getVisibleAlignmentWidth() + { + return al.getWidth() - al.getHiddenColumns().getSize(); + } + + /** + * Get alignment height in rows, excluding hidden rows + */ + public int getVisibleAlignmentHeight() + { + return al.getHeight(); + } + + /** + * Set first residue visible in the viewport, and retain the current width. + * Fires a property change event. + * + * @param res + * residue position + */ + public void setStartRes(int res) + { + int width = getViewportWidth(); + setStartEndRes(res, res + width - 1); + } + + /** + * Set start and end residues at the same time. This method only fires one + * event for the two changes, and should be used in preference to separate + * calls to setStartRes and setEndRes. + * + * @param start + * the start residue + * @param end + * the end residue + */ + public void setStartEndRes(int start, int end) + { + int oldstartres = this.startRes; + + /* + * if not wrapped, don't leave white space at the right margin + */ + int lastColumn = getVisibleAlignmentWidth() - 1; + if (!wrappedMode && (start > lastColumn)) + { + startRes = Math.max(lastColumn, 0); + } + else if (start < 0) + { + startRes = 0; + } + else + { + startRes = start; + } + + int oldendres = this.endRes; + if (end < 0) + { + endRes = 0; + } + else if (!wrappedMode && (end > lastColumn)) + { + endRes = Math.max(lastColumn, 0); + } + else + { + endRes = end; + } + + changeSupport.firePropertyChange(STARTRES, oldstartres, startRes); + if (oldstartres == startRes) + { + // event won't be fired if start positions are same + // fire an event for the end positions in case they changed + changeSupport.firePropertyChange(ENDRES, oldendres, endRes); + } + } + + /** + * Set the first sequence visible in the viewport, maintaining the height. If + * the viewport would extend past the last sequence, sets the viewport so it + * sits at the bottom of the alignment. Fires a property change event. + * + * @param seq + * sequence position + */ + public void setStartSeq(int seq) + { + int startseq = seq; + int height = getViewportHeight(); + if (startseq + height - 1 > getVisibleAlignmentHeight() - 1) + { + startseq = getVisibleAlignmentHeight() - height; + } + setStartEndSeq(startseq, startseq + height - 1); + } + + /** + * Set start and end sequences at the same time. The viewport height may + * change. This method only fires one event for the two changes, and should be + * used in preference to separate calls to setStartSeq and setEndSeq. + * + * @param start + * the start sequence + * @param end + * the end sequence + */ + public void setStartEndSeq(int start, int end) + { + int oldstartseq = this.startSeq; + int visibleHeight = getVisibleAlignmentHeight(); + if (start > visibleHeight - 1) + { + startSeq = Math.max(visibleHeight - 1, 0); + } + else if (start < 0) + { + startSeq = 0; + } + else + { + startSeq = start; + } + + int oldendseq = this.endSeq; + if (end >= visibleHeight) + { + endSeq = Math.max(visibleHeight - 1, 0); + } + else if (end < 0) + { + endSeq = 0; + } + else + { + endSeq = end; + } + + changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq); + if (oldstartseq == startSeq) + { + // event won't be fired if start positions are the same + // fire in case the end positions changed + changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq); + } + } + + /** + * Set the last sequence visible in the viewport. Fires a property change + * event. + * + * @param seq + * sequence position + */ + public void setEndSeq(int seq) + { + int height = getViewportHeight(); + setStartEndSeq(seq - height + 1, seq); + } + + /** + * Get start residue of viewport + */ + public int getStartRes() + { + return startRes; + } + + /** + * Get end residue of viewport + */ + public int getEndRes() + { + return endRes; + } + + /** + * Get start sequence of viewport + */ + public int getStartSeq() + { + return startSeq; + } + + /** + * Get end sequence of viewport + */ + public int getEndSeq() + { + return endSeq; + } + + /** + * Set viewport width in residues, without changing startRes. Use in + * preference to calculating endRes from the width, to avoid out by one + * errors! Fires a property change event. + * + * @param w + * width in residues + */ + public void setViewportWidth(int w) + { + setStartEndRes(startRes, startRes + w - 1); + } + + /** + * Set viewport height in residues, without changing startSeq. Use in + * preference to calculating endSeq from the height, to avoid out by one + * errors! Fires a property change event. + * + * @param h + * height in sequences + */ + public void setViewportHeight(int h) + { + setStartEndSeq(startSeq, startSeq + h - 1); + } + + /** + * Set viewport horizontal start position and width. Use in preference to + * calculating endRes from the width, to avoid out by one errors! Fires a + * property change event. + * + * @param start + * start residue + * @param w + * width in residues + */ + public void setViewportStartAndWidth(int start, int w) + { + int vpstart = start; + if (vpstart < 0) + { + vpstart = 0; + } + + /* + * if not wrapped, don't leave white space at the right margin + */ + if (!wrappedMode) + { + if ((w <= getVisibleAlignmentWidth()) + && (vpstart + w - 1 > getVisibleAlignmentWidth() - 1)) + { + vpstart = getVisibleAlignmentWidth() - w; + } + + } + setStartEndRes(vpstart, vpstart + w - 1); + } + + /** + * Set viewport vertical start position and height. Use in preference to + * calculating endSeq from the height, to avoid out by one errors! Fires a + * property change event. + * + * @param start + * start sequence + * @param h + * height in sequences + */ + public void setViewportStartAndHeight(int start, int h) + { + int vpstart = start; + if (vpstart < 0) + { + vpstart = 0; + } + else if ((h <= getVisibleAlignmentHeight()) + && (vpstart + h - 1 > getVisibleAlignmentHeight() - 1)) + // viewport height is less than the full alignment and we are running off + // the bottom + { + vpstart = getVisibleAlignmentHeight() - h; + } + setStartEndSeq(vpstart, vpstart + h - 1); + } + + /** + * Get width of viewport in residues + * + * @return width of viewport + */ + public int getViewportWidth() + { + return (endRes - startRes + 1); + } + + /** + * Get height of viewport in residues + * + * @return height of viewport + */ + public int getViewportHeight() + { + return (endSeq - startSeq + 1); + } + + /** + * Scroll the viewport range vertically. Fires a property change event. + * + * @param up + * true if scrolling up, false if down + * + * @return true if the scroll is valid + */ + public boolean scrollUp(boolean up) + { + if (up) + { + if (startSeq < 1) + { + return false; + } + + setStartSeq(startSeq - 1); + } + else + { + if (endSeq >= getVisibleAlignmentHeight() - 1) + { + return false; + } + + setStartSeq(startSeq + 1); + } + return true; + } + + /** + * Scroll the viewport range horizontally. Fires a property change event. + * + * @param right + * true if scrolling right, false if left + * + * @return true if the scroll is valid + */ + public boolean scrollRight(boolean right) + { + if (!right) + { + if (startRes < 1) + { + return false; + } + + setStartRes(startRes - 1); + } + else + { + if (endRes >= getVisibleAlignmentWidth() - 1) + { + return false; + } + + setStartRes(startRes + 1); + } + + return true; + } + + /** + * Scroll a wrapped alignment so that the specified residue is in the first + * repeat of the wrapped view. Fires a property change event. Answers true if + * the startRes changed, else false. + * + * @param res + * residue position to scroll to + * @return + */ + public boolean scrollToWrappedVisible(int res) + { + int oldStartRes = startRes; + int width = getViewportWidth(); + + if (res >= oldStartRes && res < oldStartRes + width) + { + return false; + } + + boolean up = res < oldStartRes; + int widthsToScroll = Math.abs((res - oldStartRes) / width); + if (up) + { + widthsToScroll++; + } + + int residuesToScroll = width * widthsToScroll; + int newStartRes = up ? oldStartRes - residuesToScroll : oldStartRes + + residuesToScroll; + if (newStartRes < 0) + { + newStartRes = 0; + } + + setStartRes(newStartRes); + + return true; + } + + /** + * Scroll so that (x,y) is visible. Fires a property change event. + * + * @param x + * x position in alignment + * @param y + * y position in alignment + */ + public void scrollToVisible(int x, int y) + { + while (y < startSeq) + { + scrollUp(true); + } + while (y > endSeq) + { + scrollUp(false); + } + + HiddenColumns hidden = al.getHiddenColumns(); + while (x < hidden.adjustForHiddenColumns(startRes)) + { + if (!scrollRight(false)) + { + break; + } + } + while (x > hidden.adjustForHiddenColumns(endRes)) + { + if (!scrollRight(true)) + { + break; + } + } + } + + /** + * Adjust sequence position for page up. Fires a property change event. + */ + public void pageUp() + { + if (wrappedMode) + { + setStartRes(Math.max(0, getStartRes() - getViewportWidth())); + } + else + { + setViewportStartAndHeight(startSeq - (endSeq - startSeq), + getViewportHeight()); + } + } + + /** + * Adjust sequence position for page down. Fires a property change event. + */ + public void pageDown() + { + if (wrappedMode) + { + /* + * if height is more than width (i.e. not all sequences fit on screen), + * increase page down to height + */ + int newStart = getStartRes() + + Math.max(getViewportHeight(), getViewportWidth()); + + /* + * don't page down beyond end of alignment, or if not all + * sequences fit in the visible height + */ + if (newStart < getVisibleAlignmentWidth()) + { + setStartRes(newStart); + } + } + else + { + setViewportStartAndHeight(endSeq, getViewportHeight()); + } + } + + public void setWrappedMode(boolean wrapped) + { + wrappedMode = wrapped; + } + + public boolean isWrappedMode() + { + return wrappedMode; + } + + /** + * Answers the vertical scroll position (0..) to set, given the visible column + * that is at top left. + * + *
+   * Example:
+   *    viewport width 40 columns (0-39, 40-79, 80-119...)
+   *    column 0 returns scroll position 0
+   *    columns 1-40 return scroll position 1
+   *    columns 41-80 return scroll position 2
+   *    etc
+   * 
+ * + * @param topLeftColumn + * (0..) + * @return + */ + public int getWrappedScrollPosition(final int topLeftColumn) + { + int w = getViewportWidth(); + + /* + * visible whole widths + */ + int scroll = topLeftColumn / w; + + /* + * add 1 for a part width if there is one + */ + scroll += topLeftColumn % w > 0 ? 1 : 0; + + return scroll; + } + + /** + * Answers the maximum wrapped vertical scroll value, given the column + * position (0..) to show at top left of the visible region. + * + * @param topLeftColumn + * @return + */ + public int getWrappedMaxScroll(int topLeftColumn) + { + int scrollPosition = getWrappedScrollPosition(topLeftColumn); + + /* + * how many more widths could be drawn after this one? + */ + int columnsRemaining = getVisibleAlignmentWidth() - topLeftColumn; + int width = getViewportWidth(); + int widthsRemaining = columnsRemaining / width + + (columnsRemaining % width > 0 ? 1 : 0) - 1; + int maxScroll = scrollPosition + widthsRemaining; + + return maxScroll; + } +}