/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.web.analytics;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
import com.opengamma.web.analytics.formatting.TypeFormatter;
/**
* Represents a rectangular set of cells visible in a grid. The viewport is defined by collections of row and
* column indices of the visible cells. These are non-contiguous ordered sets. Row indices can be non-contiguous if
* the grid rows have a tree structure and parts of the structure are collapsed and therefore not visible. Column
* indices can be non-contiguous if columns are hidden or there is a fixed set of columns and the non-fixed columns
* have been scrolled.
*/
public class RectangularViewportDefinition extends ViewportDefinition {
private static final Logger s_logger = LoggerFactory.getLogger(RectangularViewportDefinition.class);
/** Indices of rows in the viewport, not empty, sorted in ascending order. */
private final List<Integer> _rows;
/** Indices of columns in the viewport, not empty, sorted in ascending order. */
private final List<Integer> _columns;
/** Format of all cells in the viewport */
private final TypeFormatter.Format _format;
/**
* @param version
* @param rows Indices of rows in the viewport, not empty
* @param columns Indices of columns in the viewport, not empty
* @param format
* @param enableLogging
*/
/* package */ RectangularViewportDefinition(int version,
List<Integer> rows,
List<Integer> columns,
TypeFormatter.Format format,
Boolean enableLogging) {
super(version, enableLogging);
ArgumentChecker.notNull(format, "format");
_format = format;
// TODO bounds checking
_rows = ImmutableList.copyOf(rows);
_columns = ImmutableList.copyOf(columns);
}
@Override
public Iterator<GridCell> iterator() {
return new CellIterator();
}
@Override
public boolean isValidFor(GridStructure grid) {
if (!_rows.isEmpty()) {
int maxRow = _rows.get(_rows.size() - 1);
if (maxRow >= grid.getRowCount()) {
return false;
}
}
if (!_columns.isEmpty()) {
int maxCol = _columns.get(_columns.size() - 1);
if (maxCol >= grid.getColumnCount()) {
return false;
}
}
return true;
}
// TODO this doesn't work properly
// scrolling up triggers a node collapse
// scrolling down or expanding the viewport down triggers a node expansion
@Override
Pair<Integer, Boolean> getChangedNode(ViewportDefinition viewportDefinition) {
// Viewport definitions other than RectangularViewportDefinitions do not have changed nodes so just return null
if (!(viewportDefinition instanceof RectangularViewportDefinition)) {
return null;
}
List<Integer> newRows = ((RectangularViewportDefinition) viewportDefinition).getRows();
//if the first rows aren't equal the user has scrolled, or if there are no rows, return null
// TODO this logic doesn't cover these cases:
// * the user expands the viewport downwards by resizing the window
// * the user scrolls the viewport down but the previous top row is still included in the off-screen buffer zone
if (_rows.isEmpty() || newRows.isEmpty() || (!_rows.get(0).equals(newRows.get(0)))) {
return null;
}
// if the first rows are equal and the viewport has changed then the user has either expanded or collapsed a node
// walk through the current and new list of rows
for (int i = 0; i < Math.max(_rows.size(), newRows.size()); i++) {
//if final node is expanded/collapsed then index will not be in list
if (i >= _rows.size()) {
// TODO this gives false positives when expanding the viewport down by resizing the window
// top row in both viewports is the same because there's no scrolling but new rows are being added without
// any nodes being expanded
s_logger.debug("return #1");
return Pairs.of(_rows.get(i - 1), true);
}
if (i >= newRows.size()) {
// TODO this gives false positives scrolling slowly up to the top into the buffer zone
// top row in both viewports is the same because of the extra hidden rows
s_logger.debug("return #2");
return Pairs.of(newRows.get(i - 1), false);
}
// if this object's row index is greater then the node has collapsed
// the the other object's row index is greater then the node has expanded
// the expanded / collapsed node is the row before the unequal row
int oldRow = _rows.get(i);
int newRow = newRows.get(i);
if (oldRow == newRow) {
continue;
}
if (newRow < oldRow) {
s_logger.debug("return #3");
return Pairs.of(newRows.get(i - 1), true);
} else if (oldRow < newRow) {
s_logger.debug("return #4");
return Pairs.of(_rows.get(i - 1), false);
}
}
// or resized the window - if the window has resized the row lists will be different lengths
return null;
}
/* package */ List<Integer> getColumns() {
return _columns;
}
/* package */ List<Integer> getRows() {
return _rows;
}
/* package */ TypeFormatter.Format getFormat() {
return _format;
}
@Override
public String toString() {
return "RectangularViewportDefinition [_rows=" + _rows + ", _columns=" + _columns + "]";
}
/**
* Iterator that returns the viewports cells by traversing rows followed by columns.
*/
private final class CellIterator implements Iterator<GridCell> {
private final Iterator<Integer> _rowIterator = _rows.iterator();
private Iterator<Integer> _colIterator;
private int _rowIndex;
private CellIterator() {
initRow();
}
private void initRow() {
if (_rowIterator.hasNext()) {
_rowIndex = _rowIterator.next();
}
_colIterator = _columns.iterator();
}
@Override
public boolean hasNext() {
return _colIterator.hasNext() || _rowIterator.hasNext();
}
@Override
public GridCell next() {
if (!_colIterator.hasNext()) {
initRow();
}
return new GridCell(_rowIndex, _colIterator.next(), getFormat());
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove not supported");
}
}
}