/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package photoSpreadTable;
import java.io.BufferedWriter;
import java.rmi.NotBoundException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import javax.swing.table.AbstractTableModel;
import photoSpread.PhotoSpread;
import photoSpread.PhotoSpreadException;
import photoSpread.PhotoSpreadException.FormulaSyntaxError;
import photoSpreadObjects.PhotoSpreadObject;
import photoSpreadObjects.PhotoSpreadStringObject;
import photoSpreadParser.ExpressionParser;
import photoSpreadParser.photoSpreadExpression.PhotoSpreadExpression;
import photoSpreadUtilities.Const;
import photoSpreadUtilities.Misc;
import photoSpreadUtilities.Const.ObjMovements;
/**
*
* @author skandel
*/
public class PhotoSpreadTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
public static final int minDroppableColumn = 1;
public static final int minDroppableRow = 0;
static private String TABLE_ELEMENT_NAME = "table";
static private String ROW_ELEMENT_NAME = "row";
private ArrayList<String> columnNames;
private Object _clipboard = null;
@SuppressWarnings("unused")
private java.io.Reader _reader;
private java.io.StringReader _stringReader;
private ArrayList<ArrayList<PhotoSpreadCell>> data;
private PhotoSpreadTable _table;
public PhotoSpreadTableModel() {
columnNames = new ArrayList<String>();
columnNames.add("");
_stringReader = new java.io.StringReader( "" );
_reader = new java.io.BufferedReader( _stringReader );
// _parser = new ExpressionParser(_reader);
initializeData();
//loadTestData();
}
private void initializeData(){
// The the trusted versions, b/c we did
// all checking at startup time:
int numRows = 0;
int numCols = 0;
numRows = PhotoSpread.photoSpreadPrefs.getInt(PhotoSpread.sheetNumRowsKey);
numCols = PhotoSpread.photoSpreadPrefs.getInt(PhotoSpread.sheetNumColsKey);
data = new ArrayList<ArrayList<PhotoSpreadCell>>();
for(int col = 0; col < numCols; col++){
columnNames.add(getColumnAsString(col+1));
data.add(new ArrayList<PhotoSpreadCell>());
PhotoSpreadCell cell = new PhotoSpreadCell(this, col, 0, "");
cell.addObject(new PhotoSpreadStringObject(cell, ""+ (col+1)));
data.get(col).add(cell);
for(int row = 0; row < numRows; row++){
data.get(col).add(new PhotoSpreadCell(this, col, row+1, ""));
}
}
}
/**
* Clear the table data, triggering cell-changed-events
* along the way.
*/
public void clear(){
for(int row = 0; row < this.getRowCount(); row++){
for(int col = 1; col < this.getColumnCount(); col++){
this.getCell(row, col).clear(Const.DONT_EVAL, Const.DONT_REDRAW);
}
}
fireTableDataChanged();
}
public PhotoSpreadTable getTable() {
return _table;
}
public void updateAllCells(boolean doEval) {
PhotoSpreadCellHandler cellHandler;
if (doEval)
for(int row = 0; row < data.size(); row++){
for(int col = 1; col < data.get(row).size(); col++){
try {
data.get(row).get(col).evaluate(Const.DONT_REDRAW);
} catch (Exception e) {
Misc.showErrorMsgAndStackTrace(e, "");
//e.printStackTrace();
}
}
}
for(int row = 0; row < data.size(); row++){
for(int col = 1; col < data.get(row).size(); col++){
cellHandler = _table.getCellEditorFor(row, col);
try {
cellHandler.redraw();
} catch (NumberFormatException e) {
Misc.showErrorMsgAndStackTrace(e, "");
//e.printStackTrace();
} catch (NotBoundException e) {
Misc.showErrorMsgAndStackTrace(e, "");
//e.printStackTrace();
}
}
}
triggerTableUpdate();
}
public void setTable(PhotoSpreadTable _table) {
this._table = _table;
}
public String toXML(){
StringBuffer xml = new StringBuffer();
xml.append("<" + TABLE_ELEMENT_NAME + ">" + System.getProperty("line.separator"));
//xml.append(PhotoSpreadHelpers.getXMLElement(NUM_ROWS_ELEMENT_NAME, this.getRowCount()));
//xml.append(PhotoSpreadHelpers.getXMLElement(NUM_COLS_ELEMENT_NAME, this.getColumnCount()));
for(int i = 0; i < data.size(); i++){
xml.append("<" + ROW_ELEMENT_NAME + ">" + System.getProperty("line.separator"));
for(int j = 1; j < data.get(i).size(); j++){
xml.append(data.get(i).get(j).toXML());
}
xml.append("</" + ROW_ELEMENT_NAME + ">" + System.getProperty("line.separator"));
}
xml.append("</" + TABLE_ELEMENT_NAME + ">" + System.getProperty("line.separator"));
return xml.toString();
}
public void toXML(BufferedWriter out){
String xmlDump = toXML();
try{
out.write(xmlDump);
}
catch(java.io.IOException e){
throw new RuntimeException(
"PhotoSpread: could not write sheet to file. Error: " +
e.getMessage());
}
}
public PhotoSpreadExpression evaluate(String formula, PhotoSpreadCell cell)
throws FormulaSyntaxError {
try{
_stringReader = new java.io.StringReader( formula );
_reader = new java.io.BufferedReader( _stringReader );
ExpressionParser parser = new ExpressionParser(cell, formula);
return parser.Expression();
// The first catch (TokenMgrError e) is required, because
// it does not get caught via (Exception e). I guess the
// automatically generated code does not inherit its exceptions
// from Java's built-in Exception class:
} catch (photoSpreadParser.TokenMgrError e) {
throw new PhotoSpreadException.FormulaSyntaxError("Invalid Formula: " + e.getMessage() + ".");
} catch (Exception e) {
throw new PhotoSpreadException.FormulaSyntaxError("Invalid Formula: " + e.getMessage() + ".");
}
}
public Object getClipboard() {
return _clipboard;
}
public void setClipboard(Object _clipboard) {
this._clipboard = _clipboard;
}
public int getColumnCount() {
return columnNames.size();
}
public int getRowCount() {
return data.size();
}
public String getColumnName(int col) {
return columnNames.get(col);
}
static public String getColumnAsString(int col){
return Misc.intToExcelCol(col);
}
/**
* Return cell at given row/col, or null if table
* does not contain those cells.
*/
public Object getValueAt(int row, int col) {
try {
return data.get(row).get(col);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
public Class<?> getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
/**
* Return cell object at row/col, both with origin 0
*
* @param colIndex
* @param rowIndex
* @return Cell object at row/col.
*/
public PhotoSpreadCell getCell(int rowIndex, int colIndex){
return data.get(rowIndex).get(colIndex);
}
/**
* Return cell object at col/row. Row has origin 1; Col has origin 0
* This method is historic and deprecated. Use getCell() instead.
*
* @param colIndex
* @param rowIndex
* @return Cell object at row/col.
*/
public PhotoSpreadCell getCellMixedOrigin(int colIndex, int rowIndex){
//System.out.println("getting cell at " + colIndex + " " + rowIndex);
return data.get(rowIndex-1).get(colIndex);
}
public static int getColumnFromName(String colName){
return Misc.excelColToInt(colName);
}
/*
* Don't need to implement this method unless your table
* editable.
*/
public boolean isCellEditable(int row, int col) {
//Note that the data/cell address is constant,
//no matter where the cell appears onscreen.
// Don't let user change the row number column:
if (col < 1) {
return false;
}
return true;
}
/*
* Don't need to implement this method unless your table's
* data can change.
*/
public void setValueAt(Object value, int row, int col) {
data.get(row).set(col, (PhotoSpreadCell) value);
//****triggerCellUpdate(row, col);
}
public void triggerCellUpdate(int row, int col){
fireTableCellUpdated(row, col);
}
public void triggerTableUpdate () {
fireTableDataChanged();
}
public void copyToClipboard(){
/*
for(int i = 0; i < this._table.getSelectedColumnCount(); i ++){
System.out.println(this._table.getSelectedColumns()[i]);
}
for(int i = 0; i < this._table.getSelectedRowCount(); i ++){
System.out.println(this._table.getSelectedRows()[i]);
}
*/
PhotoSpreadCell cell = data.get(this.getTable().getSelectedRow()).get(this.getTable().getSelectedColumn());
_clipboard = cell;
}
public void moveSelectedObjects(
PhotoSpreadCell srcCell,
PhotoSpreadCell destCell) {
moveOrCopySelectedObjects(srcCell, destCell,ObjMovements.MOVE);
}
public void copySelectedObjects(
PhotoSpreadCell srcCell,
PhotoSpreadCell destCell) {
moveOrCopySelectedObjects(srcCell, destCell,ObjMovements.COPY);
}
/**
* For dnd moving of objects. The move from source cell
* to destination cell is NOT done if the source cell
* is a formula cell. You can't drag out of a formula cell.
* If destination is a formula cell, dropped objects are
* moved to the target cell, and their metadata is forced
* to satisfy the formula. If both source and destination
* are collections, the move is executed, and no metadata
* is changed.
* NOTE this method does not update the UI.
* Caller must do that.
* @param objects
*/
public void moveOrCopySelectedObjects(
PhotoSpreadCell srcCell,
PhotoSpreadCell destCell,
ObjMovements moveOrCopy) {
if ((srcCell == null) ||
(destCell == null))
throw new RuntimeException(
new PhotoSpreadException.DnDSourceOrDestNotSet(
"Must set both source and destination cell before drag/drop execution."));
boolean sourceIsFormula = srcCell.isFormulaCell();
boolean destIsFormula = destCell.isFormulaCell();
ArrayList<PhotoSpreadObject> objects = srcCell.getSelectedObjects();
if(!sourceIsFormula && !destIsFormula){
if (moveOrCopy == ObjMovements.MOVE)
srcCell.removeObjects(objects);
destCell.addObjects(objects);
Iterator<PhotoSpreadObject> it = objects.iterator();
if (moveOrCopy == ObjMovements.MOVE)
while(it.hasNext()){
PhotoSpreadObject object = it.next();
object.setCell(destCell);
}
try {
srcCell.evaluate(Const.DONT_REDRAW);
} catch (Exception e) {
Misc.showErrorMsgAndStackTrace(e, "");
//e.printStackTrace();
}
destCell.setFormula(Const.OBJECTS_COLLECTION_INTERNAL_TOKEN,
Const.DO_EVAL,
Const.DONT_REDRAW);
return;
}
else if(destIsFormula){
forceObjects(destCell, objects, Const.DO_EVAL, Const.DONT_REDRAW);
return;
}
Misc.showErrorMsg("Cannot drag out of a formula cell.", PhotoSpread.getCurrentSheetWindow());
}
private static void forceObjects(
PhotoSpreadCell destCell,
ArrayList<PhotoSpreadObject> objects,
Boolean reEvaluateCell,
Boolean reDrawTable){
if (destCell == null)
throw new RuntimeException(
new PhotoSpreadException.DnDSourceOrDestNotSet(
"Must set both source and destination cell before drag/drop execution."));
// Andreas Paepcke: Changed from ArrayList to Set. Else
// when dragging many photos into a cell, that cell gets
// re-evaluated as often as the number of photos being
// added.
// ArrayList<PhotoSpreadCell> cellsToUpdate = new ArrayList<PhotoSpreadCell>();
// HashSet<PhotoSpreadCell> cellsToUpdate = (HashSet<PhotoSpreadCell>) Collections.synchronizedSet(new HashSet<PhotoSpreadCell>());
HashSet<PhotoSpreadCell> cellsToUpdate = new HashSet<PhotoSpreadCell>();
Iterator<PhotoSpreadObject> it = objects.iterator();
while(it.hasNext()){
PhotoSpreadObject object = it.next();
PhotoSpreadCell updateCell = forceObject(
object,
destCell,
Const.DONT_EVAL,
Const.DONT_REDRAW);
if(updateCell != null){
cellsToUpdate.add(updateCell);
}
}
try {
destCell.evaluate(reDrawTable);
} catch (Exception e) {
Misc.showErrorMsgAndStackTrace(e, "");
//e.printStackTrace();
}
evaluateCells(cellsToUpdate);
}
private static PhotoSpreadCell forceObject(
PhotoSpreadObject object,
PhotoSpreadCell destCell,
Boolean reEvaluateCell,
Boolean reDrawTable){
if (destCell == null)
throw new RuntimeException(
new PhotoSpreadException.DnDSourceOrDestNotSet(
"Must set both source and destination cell before drag/drop execution."));
return destCell.forceObject(object, reEvaluateCell, reDrawTable);
}
private static void evaluateCells(HashSet<PhotoSpreadCell> cells){
Iterator<PhotoSpreadCell> it = cells.iterator();
while(it.hasNext()){
try {
it.next().evaluate(Const.DONT_REDRAW);
} catch (Exception e) {
Misc.showErrorMsgAndStackTrace(e, "");
//e.printStackTrace();
}
}
}
}