/**
* Sencha GXT 3.0.0b - Sencha for GWT
* Copyright(c) 2007-2012, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
package com.sencha.gxt.desktopapp.client.spreadsheet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import com.google.gwt.core.client.GWT;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.event.StoreAddEvent;
import com.sencha.gxt.data.shared.event.StoreAddEvent.StoreAddHandler;
import com.sencha.gxt.data.shared.event.StoreRemoveEvent;
import com.sencha.gxt.data.shared.event.StoreRemoveEvent.StoreRemoveHandler;
import com.sencha.gxt.desktopapp.client.spreadsheet.Evaluator.Location;
import com.sencha.gxt.desktopapp.client.spreadsheet.Evaluator.Operation;
import com.sencha.gxt.widget.core.client.box.AlertMessageBox;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.RowNumberer;
import com.sencha.gxt.widget.core.client.selection.CellSelection;
/**
* Implementation Notes:
* <ul>
* <li>References to <code>rowIndex</code> and <code>columnIndex</code> are
* 0-based.</li>
* <li>References to <code>cell</code> columns include the RowNumberer column
* and are 1-based.</li>
* <li>The <code>ColumnProvider</code> is 1-based.</li>
* <li>Row names are 1-based.</li>
* </ul>
*/
public class Worksheet implements TableValueProvider {
private static final int DEFAULT_ROW_COUNT = 2;
private static final int DEFAULT_COLUMN_COUNT = 2;
private TableFactory tableFactory;
private ListStore<Row> listStore;
private ColumnProvider columnProvider;
private ModelKeyProvider<Row> modelKeyProvider;
private int nextRowId;
protected boolean isSuppressAddEvent;
public Worksheet(ColumnProvider columnProvider) {
this.columnProvider = columnProvider;
}
public void changeColumnCount(int newColumnCount) {
List<Row> rows = getListStore().getAll();
int rowCount = rows.size();
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
List<String> columns = rows.get(rowIndex).getColumns();
if (newColumnCount < columns.size()) {
rows.get(rowIndex).setColumns(new ArrayList<String>(columns.subList(0, newColumnCount)));
} else {
createColumns(columns, newColumnCount);
}
}
}
public void changeRowCount(int newRowCount) {
if (newRowCount < getRowCount()) {
List<Row> rows = getListStore().getAll();
getListStore().replaceAll(new ArrayList<Row>(rows.subList(0, newRowCount)));
} else {
createRows(newRowCount, getColumnCount());
}
}
public void deleteColumn(int deleteColumnIndex) {
List<Row> rows = getListStore().getAll();
int rowCount = rows.size();
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
List<String> oldColumns = rows.get(rowIndex).getColumns();
ArrayList<String> newColumns = new ArrayList<String>();
int columnIndex = 0;
for (String value : oldColumns) {
if (columnIndex++ != deleteColumnIndex) {
newColumns.add(value);
}
}
rows.get(rowIndex).setColumns(newColumns);
}
adjustCellReferences(Operation.DELETE, Location.COLUMN, deleteColumnIndex);
}
public void deleteRow(int deleteRowIndex) {
getListStore().remove(deleteRowIndex);
}
public double evaluate(String expression) {
Evaluator evaluator = new Evaluator(expression, this);
return evaluateWithCatch(evaluator);
}
public double evaluate(String expression, int rowIndex, int columnIndex) {
Evaluator evaluator = new Evaluator(expression, this);
evaluator.setRowIndex(rowIndex);
evaluator.setColumnIndex(columnIndex);
return evaluateWithCatch(evaluator);
}
public double evaluateCell(String expression, int rowIndex, int cell) {
return evaluate(expression, rowIndex, cell - 1);
}
public int getCellCount() {
return getColumnCount() + 1;
}
public int getColumnCount() {
return getRowCount() == 0 ? 0 : getListStore().get(0).getColumns().size();
}
public ListStore<Row> getListStore() {
if (listStore == null) {
listStore = new ListStore<Row>(getModelKeyProvider());
listStore.setAutoCommit(true);
listStore.addStoreAddHandler(new StoreAddHandler<Row>() {
@Override
public void onAdd(StoreAddEvent<Row> event) {
if (!isSuppressAddEvent) {
int index = event.getIndex();
adjustCellReferences(Operation.INSERT, Location.ROW, index);
adjustCellReferences(Operation.RESTORE, Location.ROW, index, listStore.get(index));
}
}
});
listStore.addStoreRemoveHandler(new StoreRemoveHandler<Row>() {
@Override
public void onRemove(StoreRemoveEvent<Row> event) {
adjustCellReferences(Operation.SAVE, Location.ROW, event.getIndex(), event.getItem());
adjustCellReferences(Operation.DELETE, Location.ROW, event.getIndex(), event.getItem());
adjustCellReferences(Operation.DELETE, Location.ROW, event.getIndex());
}
});
}
return listStore;
}
public String getName(CellSelection<Row> cellSelection) {
String name = null;
ColumnConfig<Row, Object> columnConfig = getColumnProvider().getColumn(cellSelection.getCell());
if (!(columnConfig instanceof RowNumberer)) {
name = columnConfig.getHeader().asString() + (cellSelection.getRow() + 1);
}
return name;
}
public int getRowCount() {
return getListStore().size();
}
public String getValue() {
nextRowId = 0;
List<Row> rows = new LinkedList<Row>();
int rowCount = getRowCount();
int columnCount = getColumnCount();
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
List<String> columns = new LinkedList<String>();
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
columns.add(getValue(rowIndex, columnIndex));
}
Row row = getTableFactory().row().as();
row.setId(nextRowId++);
row.setColumns(columns);
rows.add(row);
}
Table table = getTableFactory().table().as();
table.setNextRowId(nextRowId);
table.setRows(rows);
AutoBean<Table> tableAutoBean = AutoBeanUtils.getAutoBean(table);
String value = AutoBeanCodex.encode(tableAutoBean).getPayload();
return value;
}
public String getValue(CellSelection<Row> cellSelection) {
return getValue(cellSelection.getRow(), cellSelection.getCell() - 1);
}
public String getValue(int rowIndex, int columnIndex) {
Row row = getListStore().get(rowIndex);
ColumnConfig<Row, Object> columnConfig = getColumnProvider().getColumn(columnIndex + 1);
return columnConfig.getValueProvider().getValue(row).toString();
}
public void insertColumn(int insertColumnIndex) {
int rowCount = getRowCount();
int columnCount = getColumnCount() + 1;
List<Row> rows = getListStore().getAll();
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
List<String> oldColumns = rows.get(rowIndex).getColumns();
ArrayList<String> newColumns = new ArrayList<String>();
Iterator<String> iterator = oldColumns.iterator();
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
if (columnIndex == insertColumnIndex) {
newColumns.add("");
} else {
newColumns.add(iterator.next());
}
}
rows.get(rowIndex).setColumns(newColumns);
}
adjustCellReferences(Operation.INSERT, Location.COLUMN, insertColumnIndex);
}
public void insertRow(int insertRowIndex) {
Row newRow = getTableFactory().row().as();
ArrayList<String> columns = new ArrayList<String>();
createColumns(columns, getColumnCount());
newRow.setColumns(columns);
newRow.setId(getNextRowId());
getListStore().add(insertRowIndex, newRow);
}
public void renameColumns(int newCellIndex) {
int newColumnIndex = newCellIndex - 1;
String oldColumnName = getColumnProvider().getColumn(newCellIndex).getHeader().asString();
int oldColumnIndex = SpreadsheetUtilities.getColumnIndex(oldColumnName);
int rowCount = getRowCount();
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
Row row = getListStore().get(rowIndex);
adjustCellReferences(Operation.SAVE, Location.COLUMN, oldColumnIndex, row);
}
adjustCellReferences(Operation.DELETE, Location.COLUMN, oldColumnIndex);
adjustCellReferences(Operation.INSERT, Location.COLUMN, newColumnIndex);
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
Row row = getListStore().get(rowIndex);
adjustCellReferences(Operation.RESTORE, Location.COLUMN, newColumnIndex, row);
}
int columnCount = getColumnCount();
for (int columnIndex = Math.min(oldColumnIndex, newColumnIndex); columnIndex < columnCount; columnIndex++) {
String columnName = SpreadsheetUtilities.getColumnName(columnIndex);
getColumnProvider().setColumnHeader(columnIndex + 1, SafeHtmlUtils.fromString(columnName));
}
}
public void setValue(String value) {
try {
isSuppressAddEvent = true;
listStore = null;
if (value == null || value.isEmpty()) {
nextRowId = 0;
createRows(DEFAULT_ROW_COUNT, DEFAULT_COLUMN_COUNT);
} else {
Table table = AutoBeanCodex.decode(getTableFactory(), Table.class, value).as();
nextRowId = table.getNextRowId();
List<Row> rows = table.getRows();
getListStore().addAll(rows);
}
} finally {
isSuppressAddEvent = false;
}
}
public void setValue(String value, CellSelection<Row> cellSelection) {
Row row = getListStore().get(cellSelection.getRow());
ColumnConfig<Row, Object> columnConfig = getColumnProvider().getColumn(cellSelection.getCell());
columnConfig.getValueProvider().setValue(row, value);
}
protected void adjustCellReferences(Operation operation, Location location, int index, Row row) {
int columnCount = getColumnCount();
ListIterator<String> iterator = row.getColumns().listIterator();
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
String value = iterator.next();
if (value.startsWith(Evaluator.EXPRESSION_MARKER)) {
Evaluator evaluator = new Evaluator(value, this);
value = evaluator.adjustCellReferences(operation, location, value, index);
iterator.set(value);
}
}
}
protected double evaluateWithCatch(Evaluator evaluator) {
double value;
try {
value = evaluator.evaluateExpression();
} catch (RuntimeException e) {
e.printStackTrace();
new AlertMessageBox("Expression", e.toString()).show();
value = Double.NaN;
}
return value;
}
private void adjustCellReferences(Operation operation, Location location, int index) {
int rowCount = getRowCount();
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
Row row = getListStore().get(rowIndex);
adjustCellReferences(operation, location, index, row);
}
}
private void createColumns(List<String> columns, int newColumnCount) {
while (columns.size() < newColumnCount) {
columns.add("");
}
}
private void createRows(int newRowCount, int newColumnCount) {
while (getRowCount() < newRowCount) {
ArrayList<String> columns = new ArrayList<String>();
createColumns(columns, newColumnCount);
Row row = getTableFactory().row().as();
row.setColumns(columns);
row.setId(getNextRowId());
getListStore().add(row);
}
}
private ColumnProvider getColumnProvider() {
return columnProvider;
}
private ModelKeyProvider<Row> getModelKeyProvider() {
if (modelKeyProvider == null) {
modelKeyProvider = new ModelKeyProvider<Row>() {
@Override
public String getKey(Row row) {
return Integer.toString(row.getId());
}
};
}
return modelKeyProvider;
}
private int getNextRowId() {
return nextRowId++;
}
private TableFactory getTableFactory() {
if (tableFactory == null) {
tableFactory = GWT.create(TableFactory.class);
}
return tableFactory;
}
}