/* * 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; /** * 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"; 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[] oldvalues = updateStartEndRes(start, end); int oldstartres = oldvalues[0]; int oldendres = oldvalues[1]; 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); } } /** * 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) { 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; } return new int[] { oldstartres, oldendres }; } /** * 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[] oldvalues = updateStartEndSeq(start, end); int oldstartseq = oldvalues[0]; int oldendseq = oldvalues[1]; 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); } } /** * 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 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; } return new int[] { oldstartseq, oldendseq }; } /** * 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); } /** * 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(); if (startseq + height - 1 > getVisibleAlignmentHeight() - 1) { startseq = getVisibleAlignmentHeight() - height; } int[] oldseqvalues = updateStartEndSeq(startseq, startseq + height - 1); int[] old = new int[] { oldresvalues[0], oldseqvalues[0] }; int[] newresseq = new int[] { startRes, startSeq }; changeSupport.firePropertyChange(STARTRESANDSEQ, old, newresseq); } /** * 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 in unwrapped mode, scroll up or down one sequence row; * if in wrapped mode, scroll by one visible width of columns */ if (up) { if (wrappedMode) { pageUp(); } else { if (startSeq < 1) { return false; } setStartSeq(startSeq - 1); } } else { if (wrappedMode) { pageDown(); } 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; } }