/*
* 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, 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
*/
protected void resetCursor(List hiddenCols)
{
hiddenColumns = hiddenCols;
if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
{
firstColumn = hiddenColumns.get(0)[0];
HiddenCursorPosition oldpos = cursorPos.get();
HiddenCursorPosition newpos = new HiddenCursorPosition(0, 0, 0);
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();
}
// update the cursor position
HiddenCursorPosition newpos = new HiddenCursorPosition(index,
oldpos.getHiddenSoFar(), oldpos.getNumColumns());
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;
}
if ((hiddenColumns.get(index)[0] <= column)
&& hiddenColumns.get(index)[1] >= column)
{
// column is in the current region
// we hit the jackpot
// don't need to move index
}
else if (column < firstColumn)
{
index = 0;
hiddenCount = 0;
}
// column is after current region
else if (column > hiddenColumns.get(index)[1]) // includes if column >
// lastColumn
{
// 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
if ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
{
while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
{
index--;
int[] region = hiddenColumns.get(index);
hiddenCount -= region[1] - region[0] + 1;
}
}
}
HiddenCursorPosition newpos = new HiddenCursorPosition(index,
hiddenCount, oldpos.getNumColumns());
cursorPos.compareAndSet(oldpos, newpos);
return newpos;
}
/**
* 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;
}
}
HiddenCursorPosition newpos = new HiddenCursorPosition(index,
hiddenCount, oldpos.getNumColumns());
cursorPos.compareAndSet(oldpos, newpos);
return newpos;
}
}