/* * 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 */ if (!wrappedMode && (start > getVisibleAlignmentWidth() - 1)) { startRes = Math.max(getVisibleAlignmentWidth() - 1, 0); } else if (start < 0) { startRes = 0; } else { startRes = start; } int oldendres = this.endRes; if (end < 0) { endRes = 0; } else if (!wrappedMode && (end > getVisibleAlignmentWidth() - 1)) { endRes = Math.max(getVisibleAlignmentWidth() - 1, 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 last residue visible in the viewport. Fires a property change event. * * @param res * residue position */ public void setEndRes(int res) { int startres = res; int width = getViewportWidth(); /* * if not wrapped, don't leave white space at the right margin */ if (!wrappedMode) { if (startres + width - 1 > getVisibleAlignmentWidth() - 1) { startres = getVisibleAlignmentWidth() - width; } } setStartEndRes(startres - width + 1, startres); } /** * 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 visible. Fires * a property change event. * * @param res * residue position to scroll to */ public void scrollToWrappedVisible(int res) { // get the start residue of the wrapped row which res is in // and set that as our start residue int width = getViewportWidth(); setStartRes((res / width) * width); } /** * 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; } /** * Answers the vertical scroll position (0..) to set, given the visible column * that is at top left. Note that if called with the total visible width of * the alignment, this gives the maximum cursor scroll value. * *
   * Example:
   *    viewport width 40 columns (0-39, 40-79, 80-119...)
   *    column 0 returns scroll position 0
   *    columns 0-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; scroll += topLeftColumn % w > 0 ? 1 : 0; return scroll; } }