X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fviewmodel%2FViewportRanges.java;h=af75b2713e3e7a538f0cb8cb3d6d56f744836180;hb=14bfc6fb57f123b815f08dbf5b35544abd33b3af;hp=72155aab227a9a89c8e5697c9ab71775a0fb9a13;hpb=1af07fa4f32bcb8c9f35be3f92565774ca35215c;p=jalview.git diff --git a/src/jalview/viewmodel/ViewportRanges.java b/src/jalview/viewmodel/ViewportRanges.java index 72155aa..af75b27 100644 --- a/src/jalview/viewmodel/ViewportRanges.java +++ b/src/jalview/viewmodel/ViewportRanges.java @@ -21,16 +21,30 @@ package jalview.viewmodel; import jalview.datamodel.AlignmentI; +import jalview.datamodel.HiddenColumns; /** - * 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 + * 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"; + + public static final String STARTRESANDSEQ = "startresandseq"; + + public static final String MOVE_VIEWPORT = "move_viewport"; + + private boolean wrappedMode = false; + // start residue of viewport private int startRes; @@ -79,7 +93,24 @@ public class ViewportRanges extends ViewportProperties } /** + * Get alignment width in cols, excluding hidden cols + */ + public int getVisibleAlignmentWidth() + { + return al.getVisibleWidth(); + } + + /** + * 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 @@ -90,91 +121,223 @@ public class ViewportRanges extends ViewportProperties setStartEndRes(res, res + width - 1); } - public void setStartEndRes(int startres, int endres) + /** + * 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 oldres = this.startRes; - if (startres > al.getWidth() - 1) + int[] oldvalues = updateStartEndRes(start, end); + int oldstartres = oldvalues[0]; + int oldendres = oldvalues[1]; + + if (oldstartres == startRes && oldendres == endRes) { - startres = al.getWidth() - 1; + return; // BH 2019.07.27 standard check for no changes } - else if (startres < 0) + + // "STARTRES" is a misnomer here -- really "STARTORENDRES" + // note that this could be "no change" if the range is just being expanded + changeSupport.firePropertyChange(STARTRES, oldstartres, startRes); + if (oldstartres == startRes) { - startres = 0; + // No listener cares about this + // "ENDRES" is a misnomer here -- really "ENDONLYRES" + // BH 2019.07.27 adds end change check + // fire only if only the end is changed + changeSupport.firePropertyChange(ENDRES, oldendres, endRes); } - this.startRes = startres; + } - if (endres >= al.getWidth()) + /** + * Update start and end residue values, adjusting for width constraints if + * necessary + * + * @param start + * start residue + * @param end + * end residue + * @return array containing old start and end residue values + */ + private int[] updateStartEndRes(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) { - endres = al.getWidth() - 1; + startRes = 0; } - else if (endres < 0) + else { - endres = 0; + startRes = start; } - this.endRes = endres; - changeSupport.firePropertyChange("startres", oldres, startres); + int oldendres = this.endRes; + if (end < 0) + { + endRes = 0; + } + else if (!wrappedMode && (end > lastColumn)) + { + endRes = Math.max(lastColumn, 0); + } + else + { + endRes = end; + } + return new int[] { oldstartres, oldendres }; } /** - * Set last residue visible in the viewport + * 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 res - * residue position + * @param seq + * sequence position */ - public void setEndRes(int res) + public void setStartSeq(int seq) { - int width = getViewportWidth(); - setStartEndRes(res - width + 1, res); + int height = getViewportHeight(); + int startseq = Math.min(seq, getVisibleAlignmentHeight() - height); + // BH 2019.07.27 cosmetic only -- was: + // if (startseq + height - 1 > getVisibleAlignmentHeight() - 1) + // { + // startseq = getVisibleAlignmentHeight() - height; + // } + setStartEndSeq(startseq, startseq + height - 1); } /** - * Set the first sequence visible in the viewport + * 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 seq - * sequence position + * @param start + * the start sequence + * @param end + * the end sequence */ - public void setStartSeq(int seq) + public void setStartEndSeq(int start, int end) { - int height = getViewportHeight(); - setStartEndSeq(seq, seq + height - 1); + // System.out.println("ViewportRange setStartEndSeq " + start + " " + end); + int[] oldvalues = updateStartEndSeq(start, end); + int oldstartseq = oldvalues[0]; + int oldendseq = oldvalues[1]; + + if (oldstartseq == startSeq && oldendseq == endSeq) + { + return; // BH 2019.07.27 standard check for no changes + } + + // "STARTSEQ" is a misnomer here -- really "STARTORENDSEQ" + changeSupport.firePropertyChange(STARTSEQ, oldstartseq, startSeq); + if (oldstartseq == startSeq) + { + // Note that all listeners ignore this - could be removed, or there is a + // bug. + // "ENDSEQ" is a misnomer here -- really "ENDONLYSEQ" + // additional fire, only if only the end is changed + changeSupport.firePropertyChange(ENDSEQ, oldendseq, endSeq); + } } - public void setStartEndSeq(int startseq, int endseq) + /** + * Update start and end sequence values, adjusting for height constraints if + * necessary + * + * @param start + * start sequence + * @param end + * end sequence + * @return array containing old start and end sequence values + */ + private int[] updateStartEndSeq(int start, int end) { - int oldseq = this.startSeq; - if (startseq > al.getHeight() - 1) + int oldstartseq = this.startSeq; + int visibleHeight = getVisibleAlignmentHeight(); + if (start > visibleHeight - 1) { - startseq = al.getHeight() - 1; + startSeq = Math.max(visibleHeight - 1, 0); } - else if (startseq < 0) + else if (start < 0) { - startseq = 0; + startSeq = 0; + } + else + { + startSeq = start; } - this.startSeq = startseq; - if (endseq >= al.getHeight()) + int oldendseq = this.endSeq; + if (end >= visibleHeight) + { + endSeq = Math.max(visibleHeight - 1, 0); + } + else if (end < 0) { - endseq = al.getHeight() - 1; + endSeq = 0; } - else if (endseq < 0) + else { - endseq = 0; + endSeq = end; } - this.endSeq = endseq; - changeSupport.firePropertyChange("startseq", oldseq, startseq); + return new int[] { oldstartseq, oldendseq }; } /** - * Set the last sequence visible in the viewport + * Set the last sequence visible in the viewport. Fires a property change + * event. * * @param seq - * sequence position + * sequence position in the range [0, height) */ public void setEndSeq(int seq) { + // BH 2018.04.18 added safety for seq < 0; comment about not being >= height + setStartEndSeq(Math.max(0, seq + 1 - getViewportHeight()), seq); + } + + /** + * Set start residue and start sequence together (fires single event). The + * event supplies a pair of old values and a pair of new values: [old start + * residue, old start sequence] and [new start residue, new start sequence] + * + * @param res + * the start residue + * @param seq + * the start sequence + */ + public void setStartResAndSeq(int res, int seq) + { + int width = getViewportWidth(); + int[] oldresvalues = updateStartEndRes(res, res + width - 1); + + int startseq = seq; int height = getViewportHeight(); - setStartEndSeq(seq - height + 1, seq); + if (startseq + height - 1 > getVisibleAlignmentHeight() - 1) + { + startseq = getVisibleAlignmentHeight() - height; + } + int[] oldseqvalues = updateStartEndSeq(startseq, startseq + height - 1); + + int[] oldvalues = new int[] { oldresvalues[0], oldseqvalues[0] }; + int[] newvalues = new int[] { startRes, startSeq }; + changeSupport.firePropertyChange(STARTRESANDSEQ, oldvalues, newvalues); } /** @@ -210,13 +373,101 @@ public class ViewportRanges extends ViewportProperties } /** + * 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; + + int visHeight = getVisibleAlignmentHeight(); + if (vpstart < 0) + { + vpstart = 0; + } + else if (h <= visHeight && vpstart + h > visHeight) + // viewport height is less than the full alignment and we are running off + // the bottom + { + vpstart = visHeight - h; + } + + setStartEndSeq(vpstart, vpstart + h - 1); + } + + /** * Get width of viewport in residues * * @return width of viewport */ public int getViewportWidth() { - return (endRes - startRes + 1); // TODO get for wrapped alignments too + return (endRes - startRes + 1); } /** @@ -229,37 +480,60 @@ public class ViewportRanges extends ViewportProperties return (endSeq - startSeq + 1); } - // return value is true if the scroll is valid + /** + * 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 in unwrapped mode, scroll up or down one sequence row; + * if in wrapped mode, scroll by one visible width of columns + */ if (up) { - if (startSeq < 1) + if (wrappedMode) { - return false; + pageUp(); + } + else + { + if (startSeq < 1) + { + return false; + } + setStartSeq(startSeq - 1); } - - setStartSeq(startSeq - 1); } else { - if (endSeq >= al.getHeight() - 1) + if (wrappedMode) { - return false; + pageDown(); + } + else + { + if (endSeq >= getVisibleAlignmentHeight() - 1) + { + return false; + } + setStartSeq(startSeq + 1); } - - setStartSeq(startSeq + 1); } return true; } /** - * DOCUMENT ME! + * Scroll the viewport range horizontally. Fires a property change event. * * @param right - * DOCUMENT ME! + * true if scrolling right, false if left * - * @return DOCUMENT ME! + * @return true if the scroll is valid */ public boolean scrollRight(boolean right) { @@ -274,7 +548,7 @@ public class ViewportRanges extends ViewportProperties } else { - if (endRes > al.getWidth() - 1) + if (endRes >= getVisibleAlignmentWidth() - 1) { return false; } @@ -285,13 +559,265 @@ public class ViewportRanges extends ViewportProperties return true; } - public void scrollToWrappedVisible(int res) + /** + * 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 NB visible position not absolute + * alignment position + * @return + */ + public boolean scrollToWrappedVisible(int res) + { + int newStartRes = calcWrappedStartResidue(res); + if (newStartRes == startRes) + { + return false; + } + setStartRes(newStartRes); + + return true; + } + + /** + * Calculate wrapped start residue from visible start residue + * + * @param res + * visible start residue + * @return left column of panel res will be located in + */ + private int calcWrappedStartResidue(int res) + { + int oldStartRes = startRes; + int width = getViewportWidth(); + + 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; + } + return newStartRes; + } + + /** + * Scroll so that (x,y) is visible. Fires a property change event. + * + * @param x + * x position in alignment (absolute position) + * @param y + * y position in alignment (absolute position) + */ + public void scrollToVisible(int x, int y) + { + while (y < startSeq) + { + scrollUp(true); + } + while (y > endSeq) + { + scrollUp(false); + } + + HiddenColumns hidden = al.getHiddenColumns(); + while (x < hidden.visibleToAbsoluteColumn(startRes)) + { + if (!scrollRight(false)) + { + break; + } + } + while (x > hidden.visibleToAbsoluteColumn(endRes)) + { + if (!scrollRight(true)) + { + break; + } + } + } + + /** + * Set the viewport location so that a position is visible + * + * @param x + * column to be visible: absolute position in alignment + * @param y + * row to be visible: absolute position in alignment + */ + public boolean setViewportLocation(int x, int y) { - if (res < startRes || res > endRes) + boolean changedLocation = false; + + // convert the x,y location to visible coordinates + int visX = al.getHiddenColumns().absoluteToVisibleColumn(x); + int visY = al.getHiddenSequences().findIndexWithoutHiddenSeqs(y); + + // if (vis_x,vis_y) is already visible don't do anything + if (startRes > visX || visX > endRes + || startSeq > visY && visY > endSeq) { - setEndSeq(res / getViewportWidth()); + int[] old = new int[] { startRes, startSeq }; + int[] newresseq; + if (wrappedMode) + { + int newstartres = calcWrappedStartResidue(visX); + setStartRes(newstartres); + newresseq = new int[] { startRes, startSeq }; + } + else + { + // set the viewport x location to contain vis_x + int newstartres = visX; + int width = getViewportWidth(); + if (newstartres + width - 1 > getVisibleAlignmentWidth() - 1) + { + newstartres = getVisibleAlignmentWidth() - width; + } + updateStartEndRes(newstartres, newstartres + width - 1); + + // set the viewport y location to contain vis_y + int newstartseq = visY; + int height = getViewportHeight(); + if (newstartseq + height - 1 > getVisibleAlignmentHeight() - 1) + { + newstartseq = getVisibleAlignmentHeight() - height; + } + updateStartEndSeq(newstartseq, newstartseq + height - 1); + + newresseq = new int[] { startRes, startSeq }; + } + changedLocation = true; + changeSupport.firePropertyChange(MOVE_VIEWPORT, old, newresseq); } + return changedLocation; + } + /** + * 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; + } + + @Override + public String toString() + { + return "[ViewportRange startRes=" + startRes + " endRes=" + endRes + + " startSeq=" + startSeq + " endSeq=" + endSeq + "]"; + } }