/* * 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.datamodel; import java.util.List; import java.util.concurrent.atomic.AtomicReference; public class HiddenColumnsCursor { // absolute position of first hidden column private int firstColumn; private List hiddenColumns; // AtomicReference to hold the current region index and hidden column count // Could be done with synchronisation but benchmarking shows this way is 2x // faster private final AtomicReference cursorPos = new AtomicReference<>( new HiddenCursorPosition(0, 0)); protected HiddenColumnsCursor() { } /** * Reset the cursor with a new hidden columns collection. Calls to resetCursor * should be made from within a writeLock in the HiddenColumns class - since * changes to the hiddenColumns collection require a writeLock the lock should * already exist. * * @param hiddenCols * new hidden columns collection */ protected void resetCursor(List hiddenCols) { resetCursor(hiddenCols, 0, 0); } /** * Reset the cursor with a new hidden columns collection, where we know in * advance the index and hidden columns count of a particular location. * * @param hiddenCols * new hidden columns collection * @param index * cursor index to reset to * @param hiddencount * hidden columns count to reset to */ protected void resetCursor(List hiddenCols, int index, int hiddencount) { hiddenColumns = hiddenCols; if ((hiddenCols != null) && (!hiddenCols.isEmpty())) { firstColumn = hiddenColumns.get(0)[0]; HiddenCursorPosition oldpos = cursorPos.get(); HiddenCursorPosition newpos = new HiddenCursorPosition(index, hiddencount); cursorPos.compareAndSet(oldpos, newpos); } } /** * Delete the region the cursor is currently at. Avoids having to reset the * cursor just because we deleted a region. * * Calls to updateForDeletedRegion should be made from within a writeLock in * the HiddenColumns class - since changes to the hiddenColumns collection * require a writeLock the lock should already exist. * * @param hiddenCols */ protected void updateForDeletedRegion(List hiddenCols) { if ((hiddenCols != null) && (!hiddenCols.isEmpty())) { // if there is a region to the right of the current region, // nothing changes; otherwise // we deleted the last region (index=hiddenCols.size()-1) // or the index was at the end of the alignment (index=hiddenCols.size()) HiddenCursorPosition oldpos = cursorPos.get(); int index = oldpos.getRegionIndex(); if (index >= hiddenColumns.size() - 1) { // deleted last region, index is now end of alignment index = hiddenCols.size(); HiddenCursorPosition newpos = new HiddenCursorPosition(index, oldpos.getHiddenSoFar()); cursorPos.compareAndSet(oldpos, newpos); } } hiddenColumns = hiddenCols; } /** * Get the index of the region that column is within (if column is hidden) or * which is to the right of column (if column is visible). If no hidden * columns are to the right, will return size of hiddenColumns. If hidden * columns is empty returns -1. * * @param column * absolute position of a column in the alignment * @return region index */ protected HiddenCursorPosition findRegionForColumn(int column) { if (hiddenColumns == null) { return null; } HiddenCursorPosition oldpos = cursorPos.get(); int index = oldpos.getRegionIndex(); int hiddenCount = oldpos.getHiddenSoFar(); if (index == hiddenColumns.size()) { // went past the end of hiddenColumns collection last time index--; int[] region = hiddenColumns.get(index); hiddenCount -= region[1] - region[0] + 1; } // this if statement excludes case where column is in current region // - no changes needed if (column < firstColumn) { index = 0; hiddenCount = 0; } // column is after current region else if (column > hiddenColumns.get(index)[1]) { // iterate from where we are now, if we're lucky we'll be close by // (but still better than iterating from 0) // stop when we find the region *before* column // i.e. the next region starts after column or if not, ends after column while ((index < hiddenColumns.size()) && (column > hiddenColumns.get(index)[1])) { int[] region = hiddenColumns.get(index); hiddenCount += region[1] - region[0] + 1; index++; } } // column is before current region else if (column < hiddenColumns.get(index)[0]) { // column is before or in the previous region while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column)) { index--; int[] region = hiddenColumns.get(index); hiddenCount -= region[1] - region[0] + 1; } } if (index != oldpos.getRegionIndex() || hiddenCount != oldpos.getHiddenSoFar()) { HiddenCursorPosition newpos = new HiddenCursorPosition(index, hiddenCount); cursorPos.compareAndSet(oldpos, newpos); return newpos; } return oldpos; } /** * Get the number of hidden columns in regions before column i.e. excludes * hidden columns in the region column is in, if any * * @param column * index of column in visible alignment * @return */ protected HiddenCursorPosition getHiddenOffset(int column) { if (hiddenColumns == null) { return null; } HiddenCursorPosition oldpos = cursorPos.get(); int index = oldpos.getRegionIndex(); int hiddenCount = oldpos.getHiddenSoFar(); if (column < firstColumn) { index = 0; hiddenCount = 0; } else if ((index < hiddenColumns.size()) && (hiddenColumns.get(index)[0] <= column + hiddenCount)) { // iterate from where we are now, if we're lucky we'll be close by // (but still better than iterating from 0) while ((index < hiddenColumns.size()) && (hiddenColumns.get(index)[0] <= column + hiddenCount)) { int[] region = hiddenColumns.get(index); hiddenCount += region[1] - region[0] + 1; index++; } } else { while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount)) { index--; int[] region = hiddenColumns.get(index); hiddenCount -= region[1] - region[0] + 1; } } if (index != oldpos.getRegionIndex() || hiddenCount != oldpos.getHiddenSoFar()) { HiddenCursorPosition newpos = new HiddenCursorPosition(index, hiddenCount); cursorPos.compareAndSet(oldpos, newpos); return newpos; } return oldpos; } }