package org.geogebra.common.gui.view.spreadsheet;
import java.util.ArrayList;
import java.util.HashSet;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoDependentList;
import org.geogebra.common.kernel.algos.AlgoDependentPoint;
import org.geogebra.common.kernel.algos.AlgoPolyLine;
import org.geogebra.common.kernel.algos.AlgoSort;
import org.geogebra.common.kernel.arithmetic.ExpressionNode;
import org.geogebra.common.kernel.arithmetic.MyVecNode;
import org.geogebra.common.kernel.arithmetic3D.MyVec3DNode;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoElementSpreadsheet;
import org.geogebra.common.kernel.geos.GeoFunctionNVar;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.GeoText;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.kernel.kernelND.GeoPointND;
import org.geogebra.common.main.App;
import org.geogebra.common.main.Localization;
import org.geogebra.common.main.SpreadsheetTableModel;
import org.geogebra.common.plugin.GeoClass;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.util.debug.Log;
/**
*
* Utility class with methods for processing cell ranges (e.g inserting rows,
* creating lists of cells). Typical usage is via the instance of this class
* created by the constructor of MyTable.
*
* @author G. Sturr
*
*/
@SuppressWarnings("javadoc")
public class CellRangeProcessor {
private MyTable table;
private App app;
private Localization loc;
private Construction cons;
private SpreadsheetTableModel tableModel;
public CellRangeProcessor(MyTable table) {
this.table = table;
app = table.getKernel().getApplication();
loc = app.getLocalization();
tableModel = app.getSpreadsheetTableModel();
cons = table.getKernel().getConstruction();
}
/**
* @param rangeList
* cell range list to be cloned
* @return copy of given cell range list
*/
public static ArrayList<CellRange> clone(ArrayList<CellRange> rangeList) {
ArrayList<CellRange> newList = new ArrayList<CellRange>();
for (CellRange cr : rangeList) {
newList.add(cr.duplicate());
}
return newList;
}
// ===============================================
// Validation
// ===============================================
/**
* @param rangeList
* given list of spreadsheet cell ranges
* @return true if the shapes of the given cell ranges support creating a
* point list, does not test the cell contents
*/
public boolean isCreatePointListPossible(ArrayList<CellRange> rangeList) {
// two adjacent rows or columns?
if (rangeList.size() == 1
&& (rangeList.get(0).is2D() || rangeList.get(0).is3D())) {
return true;
} else if (rangeList.size() == 2 && rangeList.get(0).getWidth() == 1
&& rangeList.get(1).getWidth() == 1) {
return true;
} else if (rangeList.size() == 1) {
return rangeList.get(0).isPointList();
}
return false;
}
/**
* top-left must be a function of 2 variables eg A4(x,y)=x y^2 top row and
* left column must be numbers other cells can be anything (will be erased)
*
* @param rangeList
* ranges
* @return whether operation table is possible
*/
public boolean isCreateOperationTablePossible(
ArrayList<CellRange> rangeList) {
if (rangeList.size() != 1) {
return false;
}
CellRange cr = rangeList.get(0);
int r1 = cr.getMinRow();
int c1 = cr.getMinColumn();
if (!(RelativeCopy.getValue(app, c1, r1) instanceof GeoFunctionNVar)) {
return false;
}
for (int r = r1 + 1; r <= cr.getMaxRow(); ++r) {
if (!(RelativeCopy.getValue(app, c1, r) instanceof GeoNumeric)) {
return false;
}
}
for (int c = c1 + 1; c <= cr.getMaxColumn(); ++c) {
if (!(RelativeCopy.getValue(app, c, r1) instanceof GeoNumeric)) {
return false;
}
}
return true;
}
public boolean isCreateMatrixPossible(ArrayList<CellRange> rangeList) {
if (rangeList.size() == 1 && !rangeList.get(0).hasEmptyCells()) {
return true;
}
return false;
/*
* // ctrl-selection block if (rangeList.size() > 1){
* //rangeList.get(0).getWidth() == 1 && rangeList.get(1).getWidth() ==
* 1 ) return true; }
*
* if (rangeList.size() == 2 && rangeList.get(0).getHeight() == 1 &&
* rangeList.get(1).getHeight() == 1 ) return true;
*
* return false;
*/
}
/**
* Returns true if at least two cells in rangeList are of the given GeoClass
* type
*
* @param rangeList
* @param geoClass
* @return
*/
public boolean isOneVarStatsPossible(ArrayList<CellRange> rangeList,
GeoClass geoClass) {
if (rangeList == null || rangeList.size() == 0) {
return false;
}
int count = 0;
for (CellRange cr : rangeList) {
count += cr.getGeoCount(geoClass);
if (count >= 2) {
return true;
}
}
return false;
}
/**
* Returns true if either: 1) rangeList is a single cell range with at least
* two columns and at least three non-empty rows or 2) rangeList contains
* two or more columns and each column has at least three data values.
*
* @param rangeList
* @return
*/
public boolean isMultiVarStatsPossible(ArrayList<CellRange> rangeList) {
if (rangeList == null || rangeList.size() == 0) {
return false;
}
// if rangeList is a single cell range check that there are at least two
// columns and at least three non-empty rows
if (rangeList.size() == 1) {
CellRange cr = rangeList.get(0);
if (cr.getMaxColumn() - cr.getMinColumn() < 1) {
return false;
}
for (int col = cr.getMinColumn(); col <= cr.getMaxColumn(); col++) {
if (!containsMinimumGeoNumeric(new CellRange(app, col,
cr.getMinRow(), col, cr.getMaxRow()), 3)) {
return false;
}
}
return true;
}
// otherwise check if rangeList contains two or more columns and each
// column
// has at least three data values.
int columnCount = 0;
for (CellRange cr : rangeList) {
if (!cr.isColumn()) {
return false;
}
if (!containsMinimumGeoNumeric(cr, 3)) {
return false;
}
columnCount += cr.getMaxColumn() - cr.getMinColumn() + 1;
}
return columnCount >= 2;
}
/**
* Returns true if the number of GeoNumeric cells in cellRange is at least
* minimumCount.
*
* @param cellRange
* @param minimumCount
* @return
*/
private boolean containsMinimumGeoNumeric(CellRange cellRange,
int minimumCount) {
int count = 0;
for (int col = cellRange.getMinColumn(); col <= cellRange
.getMaxColumn(); ++col) {
for (int row = cellRange.getMinRow(); row <= cellRange
.getMaxRow(); ++row) {
GeoElement geo = RelativeCopy.getValue(app, col, row);
if (geo != null && geo.isGeoNumeric()) {
++count;
}
if (count >= minimumCount) {
return true;
}
}
}
return false;
}
/**
* @param rangeList
* @param geoClass
* @return true if the given rangeList contains a GeoElement of the given
* GeoClass type
*/
public boolean containsGeoClass(ArrayList<CellRange> rangeList,
GeoClass geoClass) {
for (CellRange cr : rangeList) {
if (cr.containsGeoClass(geoClass)) {
return true;
}
}
return false;
}
/**
* Returns the number of GeoElements of a given GeoClass type contained in
* the given list of cell ranges.
*
* @param rangeList
*
* @param geoClass
* the GeoClass type to count. If null, then all GeoElements are
* counted
* @return
*/
public int getGeoCount(ArrayList<CellRange> rangeList, GeoClass geoClass) {
int count = 0;
for (CellRange cr : rangeList) {
count += cr.getGeoCount(geoClass);
}
return count;
}
public boolean is1DRangeList(ArrayList<CellRange> rangeList) {
if (rangeList == null || rangeList.size() > 1) {
return false;
}
return rangeList.get(0).is1D();
}
// ====================================================
// Create Lists from Cells
// ====================================================
/**
* Creates a GeoList of lists where each element is a GeoList of cells in
* each column or row spanned by the given range list
*/
public GeoList createCollectionList(ArrayList<CellRange> rangeList,
boolean copyByValue, boolean addToConstruction,
boolean scanByColumn) {
GeoList tempGeo = new GeoList(cons);
boolean oldSuppress = cons.isSuppressLabelsActive();
cons.setSuppressLabelCreation(true);
CellRange tempRange = null;
for (CellRange cr : rangeList) {
if (scanByColumn) {
for (int col = cr.getMinColumn(); col <= cr
.getMaxColumn(); col++) {
if (cr.isColumn()) {
tempRange = new CellRange(app, col, -1);
tempRange.setActualRange();
} else {
tempRange = new CellRange(app, col, cr.getMinRow(), col,
cr.getMaxRow());
}
ArrayList<CellRange> tempList = new ArrayList<CellRange>();
tempList.add(tempRange);
tempGeo.add(createList(tempList, true, copyByValue, false,
false, null, addToConstruction));
}
} else {
for (int row = cr.getMinRow(); row <= cr.getMaxRow(); row++) {
if (cr.isRow()) {
tempRange = new CellRange(app, -1, row);
tempRange.setActualRange();
} else {
tempRange = new CellRange(app, cr.getMinColumn(), row,
cr.getMaxColumn(), row);
}
ArrayList<CellRange> tempList = new ArrayList<CellRange>();
tempList.add(tempRange);
tempGeo.add(createList(tempList, true, copyByValue, false,
false, null, addToConstruction));
}
}
}
cons.setSuppressLabelCreation(oldSuppress);
return tempGeo;
}
/**
* Creates a GeoPolyLine out of points constructed from the spreadsheet
* cells found in rangeList. Uses these defaults: no sorting, no undo point
*
* @param rangeList
* @param byValue
* @param leftToRight
* @return
*/
public GeoElement createPolyLine(ArrayList<CellRange> rangeList,
boolean byValue, boolean leftToRight) {
return createPolyLine(rangeList, byValue, leftToRight, false, false);
}
/**
* * Creates a GeoPolyLine out of points constructed from the spreadsheet
* cells found in rangeList.
*
* @param rangeList
* @param byValue
* @param leftToRight
* @param isSorted
* @param doStoreUndo
* @return
*/
public GeoElement createPolyLine(ArrayList<CellRange> rangeList,
boolean byValue, boolean leftToRight, boolean isSorted,
boolean doStoreUndo) {
boolean doCreateFreePoints = true;
GeoList list = createPointGeoList(rangeList, byValue, leftToRight,
isSorted, doStoreUndo, doCreateFreePoints);
GeoElement ret;
if (list != null && list.size() > 1 && list.get(0).isGeoElement3D()) {
ret = list.getKernel().getManager3D().PolyLine3D(null, list)[0];
} else {
AlgoPolyLine al = new AlgoPolyLine(cons, list);
ret = al.getOutput(0);
ret.setLabel(null);
}
// need it in XML - used by Create Polyline tool, so don't want this
// line
// cons.removeFromConstructionList(al);
return ret;
}
static class PointDimension {
boolean doHorizontalPairs;
int c1;
int c2;
int r1;
int r2;
int r3 = -1;
int c3 = -1;
}
/**
* Given a cell range to converted into a point list, this determines if
* pairs are joined vertically or horizontally and gets the row column
* indices needed to traverse the cells.
*/
private static void getPointListDimensions(ArrayList<CellRange> rangeList,
PointDimension pd) {
pd.doHorizontalPairs = true;
// note: we assume that rangeList has passed the
// isCreatePointListPossible() test
// CASE 1: selection is contiguous and 2D
if (rangeList.size() == 1) {
pd.doHorizontalPairs = rangeList.get(0).getWidth() == 2
|| (rangeList.get(0).getWidth() == 3
&& rangeList.get(0).getHeight() != 2);
pd.c1 = rangeList.get(0).getMinColumn();
pd.c2 = rangeList.get(0).getMaxColumn();
pd.r1 = rangeList.get(0).getMinRow();
pd.r2 = rangeList.get(0).getMaxRow();
if (rangeList.get(0).getWidth() == 3 && pd.doHorizontalPairs) {
pd.c2 = pd.c1 + 1;
pd.c3 = pd.c1 + 2;
} else if (rangeList.get(0).getHeight() == 3
&& !pd.doHorizontalPairs) {
pd.r2 = pd.r1 + 1;
pd.r3 = pd.r1 + 2;
}
// CASE 2: non-contiguous with two ranges (either single row or
// single column)
} else {
if (rangeList.get(0).getWidth() == 1
&& rangeList.get(1).getWidth() == 1) {
pd.doHorizontalPairs = true;
// we are traversing down columns. so get min and max column
// indices
pd.c1 = Math.min(rangeList.get(0).getMinColumn(),
rangeList.get(1).getMinColumn());
pd.c2 = Math.max(rangeList.get(0).getMaxColumn(),
rangeList.get(1).getMaxColumn());
// but get the max-min and min-max row indices in case the
// columns don't line up
pd.r1 = Math.max(rangeList.get(0).getMinRow(),
rangeList.get(1).getMinRow());
pd.r2 = Math.min(rangeList.get(0).getMaxRow(),
rangeList.get(1).getMaxRow());
} else {
pd.doHorizontalPairs = true;
// we are traversing across rows. so get min and max row indices
pd.r1 = Math.min(rangeList.get(0).getMinRow(),
rangeList.get(1).getMinRow());
pd.r2 = Math.max(rangeList.get(0).getMaxRow(),
rangeList.get(1).getMaxRow());
// but get the max-min and min-max column indices in case the
// rows don't line up
pd.c1 = Math.max(rangeList.get(0).getMinColumn(),
rangeList.get(1).getMinColumn());
pd.c2 = Math.min(rangeList.get(0).getMaxColumn(),
rangeList.get(1).getMaxColumn());
}
}
}
/**
* Creates a GeoList containing points constructed from the spreadsheet
* cells found in rangeList. note: It is assumed that rangeList has passed
* the isCreatePointListPossible() test
*
* @param rangeList
* @param byValue
* @param leftToRight
* @param isSorted
* @param doStoreUndo
* @param doCreateFreePoints
* if freePoints is true then a set of independent GeoPoints is
* created in addition to the list
* @return GeoList
*/
public GeoList createPointGeoList(ArrayList<CellRange> rangeList,
boolean byValue, boolean leftToRight, boolean isSorted,
boolean doStoreUndo, boolean doCreateFreePoints) {
// get the orientation and dimensions of the list
PointDimension pd = new PointDimension();
getPointListDimensions(rangeList, pd);
// build the string
ArrayList<GeoElementND> list = new ArrayList<GeoElementND>();
try {
GeoElement xCoord, yCoord, zCoord;
if (pd.doHorizontalPairs) {
for (int i = pd.r1; i <= pd.r2; ++i) {
xCoord = RelativeCopy.getValue(app, pd.c1, i);
yCoord = RelativeCopy.getValue(app, pd.c2, i);
if (pd.c3 < 0) {
createPoint(xCoord, yCoord, byValue, leftToRight,
doCreateFreePoints, list);
} else {
zCoord = RelativeCopy.getValue(app, pd.c3, i);
createPoint3D(xCoord, yCoord, zCoord, byValue,
leftToRight, doCreateFreePoints, list);
}
}
} else { // vertical pairs
for (int i = pd.c1; i <= pd.c2; ++i) {
xCoord = RelativeCopy.getValue(app, i, pd.r1);
yCoord = RelativeCopy.getValue(app, i, pd.r2);
if (pd.r3 < 0) {
createPoint(xCoord, yCoord, byValue, leftToRight,
doCreateFreePoints, list);
} else {
zCoord = RelativeCopy.getValue(app, pd.r3, i);
createPoint3D(xCoord, yCoord, zCoord, byValue,
leftToRight, doCreateFreePoints, list);
}
}
}
// System.out.println(list.toString());
}
catch (Exception ex) {
Log.debug(
"Creating list of points expression failed with exception "
+ ex);
}
AlgoDependentList dl = new AlgoDependentList(cons, list, false);
cons.removeFromConstructionList(dl);
return (GeoList) dl.getGeoElements()[0];
}
private void createPoint(GeoElement x, GeoElement y, boolean byValue,
boolean leftToRight, boolean doCreateFreePoints,
ArrayList<GeoElementND> list) {
Kernel kernel = cons.getKernel();
GeoElement xCoord = leftToRight ? x : y;
GeoElement yCoord = leftToRight ? y : x;
// don't process the point if either coordinate is null or
// non-numeric,
if (xCoord == null || yCoord == null || !xCoord.isGeoNumeric()
|| !yCoord.isGeoNumeric()) {
return;
}
GeoPoint geoPoint;
AlgoDependentPoint pointAlgo = null;
if (byValue) {
geoPoint = new GeoPoint(cons, ((GeoNumeric) xCoord).getDouble(),
((GeoNumeric) yCoord).getDouble(), 1.0);
} else {
MyVecNode vec = new MyVecNode(kernel, xCoord, yCoord);
ExpressionNode point = new ExpressionNode(kernel, vec,
Operation.NO_OPERATION, null);
point.setForcePoint();
pointAlgo = new AlgoDependentPoint(cons, point, false);
geoPoint = (GeoPoint) pointAlgo.getGeoElements()[0];
}
if (doCreateFreePoints) {
// make sure points are independent of list (and so
// draggable)
geoPoint.setLabel(null);
} else {
if (pointAlgo != null) {
cons.removeFromConstructionList(pointAlgo);
}
}
list.add(geoPoint);
if (yCoord.isAngle() || xCoord.isAngle()) {
geoPoint.setPolar();
}
}
private void createPoint3D(GeoElement x, GeoElement y, GeoElement zCoord,
boolean byValue, boolean leftToRight, boolean doCreateFreePoints,
ArrayList<GeoElementND> list) {
Kernel kernel = cons.getKernel();
GeoElement xCoord = leftToRight ? x : y;
GeoElement yCoord = leftToRight ? y : x;
// don't process the point if either coordinate is null or
// non-numeric,
if (xCoord == null || yCoord == null || !xCoord.isGeoNumeric()
|| !yCoord.isGeoNumeric() || zCoord == null
|| !zCoord.isGeoNumeric()) {
return;
}
GeoPointND geoPoint;
if (byValue) {
geoPoint = kernel.getManager3D().Point3D(
((GeoNumeric) xCoord).getDouble(),
((GeoNumeric) yCoord).getDouble(),
((GeoNumeric) zCoord).getDouble(), false);
} else {
MyVec3DNode vec = new MyVec3DNode(kernel,
leftToRight ? xCoord : yCoord,
leftToRight ? yCoord : xCoord, zCoord);
ExpressionNode point = new ExpressionNode(kernel, vec,
Operation.NO_OPERATION, null);
point.setForcePoint();
geoPoint = kernel.getManager3D().DependentPoint3D(point,
doCreateFreePoints);
}
if (doCreateFreePoints) {
// make sure points are independent of list (and so
// draggable)
geoPoint.setLabel(null);
}
list.add(geoPoint);
// if (yCoord.isAngle() || xCoord.isAngle())
// geoPoint.setPolar();
}
public String[] getPointListTitles(ArrayList<CellRange> rangeList,
boolean leftToRight) {
String[] title = new String[2];
// return null titles if data source is a point list
if (rangeList.size() == 1 && rangeList.get(0).isPointList()) {
return title;
}
// get the orientation and dimensions of the list
PointDimension pd = new PointDimension();
getPointListDimensions(rangeList, pd);
if (pd.doHorizontalPairs) {
// handle first title
if (RelativeCopy.getValue(app, pd.c1, pd.r1).isGeoText()) {
// header cell text
title[0] = ((GeoText) RelativeCopy.getValue(app, pd.c1, pd.r1))
.getTextString();
} else if (pd.r1 == 0) {
// column name
title[0] = getCellRangeString(
new CellRange(app, pd.c1, -1, pd.c1, -1));
} else {
// cell range
title[0] = getCellRangeString(
new CellRange(app, pd.c1, pd.r1, pd.c1, pd.r2));
}
// handle second title
if (RelativeCopy.getValue(app, pd.c2, pd.r1).isGeoText()) {
// header cell text
title[1] = ((GeoText) RelativeCopy.getValue(app, pd.c2, pd.r1))
.getTextString();
} else if (pd.r1 == 0) {
// column name
title[1] = getCellRangeString(
new CellRange(app, pd.c2, -1, pd.c2, -1));
} else {
// cell range
title[1] = getCellRangeString(
new CellRange(app, pd.c2, pd.r1, pd.c2, pd.r2));
}
} else { // vertical pairs
// handle first title
if (RelativeCopy.getValue(app, pd.c1, pd.r1).isGeoText()) {
// header cell text
title[0] = ((GeoText) RelativeCopy.getValue(app, pd.c1, pd.r1))
.getTextString();
} else if (pd.c1 == 0) {
// row name
title[0] = getCellRangeString(
new CellRange(app, -1, pd.r1, -1, pd.r1));
} else {
// cell range
title[0] = getCellRangeString(
new CellRange(app, pd.c1, pd.r1, pd.c2, pd.r1));
}
// handle second title
if (RelativeCopy.getValue(app, pd.c1, pd.r2).isGeoText()) {
// header cell text
title[1] = ((GeoText) RelativeCopy.getValue(app, pd.c1, pd.r2))
.getTextString();
} else if (pd.c1 == 0) {
// row name
title[1] = getCellRangeString(
new CellRange(app, -1, pd.r2, -1, pd.r2));
} else {
// cell range
title[1] = getCellRangeString(
new CellRange(app, pd.c1, pd.r2, pd.c2, pd.r2));
}
}
if (!leftToRight) {
String temp = title[0];
title[0] = title[1];
title[1] = temp;
}
return title;
}
public String[] getColumnTitles(ArrayList<CellRange> rangeList) {
ArrayList<String> titleList = new ArrayList<String>();
for (CellRange cr : rangeList) {
// get column header or column name from each column in this cell
// range
for (int col = cr.getMinColumn(); col <= cr.getMaxColumn(); col++) {
if (RelativeCopy.getValue(app, col, 0) != null
&& RelativeCopy.getValue(app, col, 0).isGeoText()) {
// use header cell text
titleList.add(((GeoText) RelativeCopy.getValue(app, col, 0))
.getTextString());
} else {
// use column name
titleList.add(getCellRangeString(
new CellRange(app, col, -1, col, -1)));
}
}
}
String[] title = new String[titleList.size()];
title = titleList.toArray(title);
return title;
}
/**
* Creates a GeoList from the cells in an array of cellranges. Empty cells
* are ignored. Uses these defaults: do not create undo point, do not sort,
* do not filter by geo type, set a label.
*/
public GeoElement createList(ArrayList<CellRange> rangeList,
boolean scanByColumn, boolean copyByValue) {
return createList(rangeList, scanByColumn, copyByValue, false, false,
null, true);
}
/**
* Creates a GeoList from the cells in an array of CellRange. Empty cells
* are ignored
*/
public GeoList createList(ArrayList<CellRange> rangeList,
boolean scanByColumn, boolean copyByValue, boolean isSorted,
boolean doStoreUndo, GeoClass geoTypeFilter, boolean setLabel) {
GeoList geoList = null;
ArrayList<GeoElementND> list = null;
if (copyByValue) {
geoList = new GeoList(cons);
} else {
list = new ArrayList<GeoElementND>();
}
ArrayList<GPoint> cellList = new ArrayList<GPoint>();
// temporary fix for catching duplicate cells caused by ctrl-seelct
// will not be needed when sorting of cells by row/column is done
HashSet<GPoint> usedCells = new HashSet<GPoint>();
try {
// create cellList: this holds a list of cell index pairs for the
// entire range
for (CellRange cr : rangeList) {
cellList.addAll(cr.toCellList(scanByColumn));
}
// iterate through the cells and add their contents to the
// expression string
for (GPoint cell : cellList) {
if (!usedCells.contains(cell)) {
GeoElement geo = RelativeCopy.getValue(app, cell.x, cell.y);
if (geo != null && (geoTypeFilter == null
|| geo.getGeoClassType() == geoTypeFilter)) {
if (copyByValue) {
geoList.add(geo.copy());
} else {
list.add(geo);
}
}
usedCells.add(cell);
}
}
// if !copyByValue convert dependent GeoList from geos collected
// above
if (!copyByValue) {
AlgoDependentList algo = new AlgoDependentList(cons, list,
false);
if (!setLabel) {
cons.removeFromConstructionList(algo);
}
geoList = (GeoList) algo.getGeoElements()[0];
}
if (isSorted) {
AlgoSort algo = new AlgoSort(cons, geoList);
cons.removeFromConstructionList(algo);
geoList = (GeoList) algo.getGeoElements()[0];
}
} catch (Exception ex) {
Log.debug("Creating list failed with exception " + ex);
}
if (doStoreUndo) {
app.storeUndoInfo();
}
if (setLabel) {
geoList.setLabel(null);
}
if (geoList != null) {
return geoList;
}
return null;
}
/** Creates a list from all cells in a spreadsheet column */
public GeoElement createListFromColumn(int column, boolean copyByValue,
boolean isSorted, boolean storeUndoInfo, GeoClass geoTypeFilter,
boolean addToConstruction) {
ArrayList<CellRange> rangeList = new ArrayList<CellRange>();
CellRange cr = new CellRange(app, column, -1);
cr.setActualRange();
rangeList.add(cr);
return createList(rangeList, true, copyByValue, isSorted, storeUndoInfo,
geoTypeFilter, addToConstruction);
}
/** Returns true if all cell ranges in the list are columns */
public boolean isAllColumns(ArrayList<CellRange> rangeList) {
boolean isAllColumns = true;
for (CellRange cr : rangeList) {
if (!cr.isColumn()) {
isAllColumns = false;
}
}
return isAllColumns;
}
/**
* Creates a string expression for a matrix where each sub-list is a list of
* cells in the columns spanned by the range list
*/
public String createColumnMatrixExpression(ArrayList<CellRange> rangeList,
boolean copyByValue, boolean addToConstruction) {
GeoElement tempGeo;
StringBuilder sb = new StringBuilder();
sb.append("{");
for (CellRange cr : rangeList) {
for (int col = cr.getMinColumn(); col <= cr.getMaxColumn(); col++) {
tempGeo = createListFromColumn(col, copyByValue, false, false,
GeoClass.NUMERIC, addToConstruction);
sb.append(
tempGeo.getDefinition(StringTemplate.defaultTemplate));
sb.append(",");
tempGeo.remove();
}
}
sb.deleteCharAt(sb.length() - 1);
sb.append("}");
return sb.toString();
}
/**
* Creates a string expression for a matrix formed by the cell range with
* upper left corner (column1, row1) and lower right corner (column2, row2).
* If transpose = true then the matrix is the formed by interchanging rows
* with columns
*/
public String createMatrixExpression(int column1, int column2, int row1,
int row2, boolean copyByValue, boolean transpose) {
GeoElement v2;
StringBuilder sb = new StringBuilder();
sb.append("{");
StringTemplate tpl = StringTemplate.defaultTemplate;
if (!transpose) {
for (int j = row1; j <= row2; ++j) {
sb.append("{");
for (int i = column1; i <= column2; ++i) {
v2 = RelativeCopy.getValue(app, i, j);
if (v2 != null) {
if (copyByValue) {
sb.append(v2.toDefinedValueString(tpl));
} else {
sb.append(v2.getLabel(tpl));
}
sb.append(',');
} else {
app.showError(loc.getPlain("CellAisNotDefined",
GeoElementSpreadsheet.getSpreadsheetCellName(i,
j)));
return null;
}
}
sb.deleteCharAt(sb.length() - 1); // remove trailing comma
sb.append("},");
}
} else {
for (int j = column1; j <= column2; ++j) {
// if (selected.length > j && ! selected[j]) continue;
sb.append("{");
for (int i = row1; i <= row2; ++i) {
v2 = RelativeCopy.getValue(app, j, i);
if (v2 != null) {
if (copyByValue) {
sb.append(v2.toDefinedValueString(tpl));
} else {
sb.append(v2.getLabel(tpl));
}
sb.append(',');
} else {
app.showError(loc.getPlain("CellAisNotDefined",
GeoElementSpreadsheet.getSpreadsheetCellName(i,
j)));
return null;
}
}
sb.deleteCharAt(sb.length() - 1); // remove trailing comma
sb.append("},");
}
}
sb.deleteCharAt(sb.length() - 1); // remove trailing comma
sb.append('}');
// Application.debug(sb.toString());
return sb.toString();
}
/**
* Creates a Matrix geo from the cell range with upper left corner (column1,
* row1) and lower right corner (column2, row2). NOTE: An undo point is not
* created.
*
* @param column1
* @param column2
* @param row1
* @param row2
* @param copyByValue
* @return
*/
public GeoElementND createMatrix(int column1, int column2, int row1,
int row2, boolean copyByValue) {
return createMatrix(column1, column2, row1, row2, copyByValue, false);
}
/**
* Creates a Matrix geo from the cell range with upper left corner (column1,
* row1) and lower right corner (column2, row2). If transpose = true then
* the matrix is the formed by interchanging rows with columns. NOTE: An
* undo point is not created.
*
* @param column1
* @param column2
* @param row1
* @param row2
* @param copyByValue
* @param transpose
* @return
*/
public GeoElementND createMatrix(int column1, int column2, int row1,
int row2, boolean copyByValue, boolean transpose) {
GeoElementND[] geos = null;
String expr = null;
try {
expr = createMatrixExpression(column1, column2, row1, row2,
copyByValue, transpose);
// Application.debug(expr);
geos = app.getKernel().getAlgebraProcessor()
.processAlgebraCommandNoExceptions(expr, false);
} catch (Exception ex) {
Log.debug("creating matrix failed " + expr);
ex.printStackTrace();
}
if (geos != null) {
return geos[0];
}
return null;
}
/**
* Creates a TableText geo from the cell range with upper left corner
* (column1, row1) and lower right corner (column2, row2). If transpose =
* true then the TableText matrix is the formed by interchanging rows with
* columns. NOTE: An undo point is not created.
*
* @param column1
* @param column2
* @param row1
* @param row2
* @param copyByValue
* @return
*/
public GeoElementND createTableText(int column1, int column2, int row1,
int row2, boolean copyByValue, boolean transpose) {
GeoElementND[] geos = null;
StringBuilder text = new StringBuilder();
try {
text.append("TableText[");
text.append(createMatrixExpression(column1, column2, row1, row2,
copyByValue, transpose));
text.append(",\"|_\"]");
// Application.debug(text);
geos = app.getKernel().getAlgebraProcessor()
.processAlgebraCommandNoExceptions(text.toString(), false);
} catch (Exception ex) {
Log.debug("creating TableText failed " + text);
ex.printStackTrace();
}
if (geos != null) {
return geos[0];
}
return null;
}
// ===================================================
// Insert Rows/Columns
// ===================================================
public enum Direction {
/** left */
Left,
/** right */
Right,
/** up */
Up,
/** down */
Down
}
/**
* @param column1
* minimum selected column
* @param column2
* maximum selected column
* @param insertLeft
* true = insert left of column1, false = insert right of column2
*/
public void insertColumn(int column1, int column2, boolean insertLeft) {
if (insertLeft) {
shiftColumnsRight(column1);
table.getCellFormatHandler().shiftFormats(column1, 1,
Direction.Right);
} else {
shiftColumnsRight(column2 + 1);
table.getCellFormatHandler().shiftFormats(column2 + 1, 1,
Direction.Right);
}
table.repaintAll();
}
/**
* @param column1
* minimum selected column
* @param column2
* maximum selected column
*/
public void deleteColumns(int column1, int column2) {
table.getCopyPasteCut().delete(column1, 0, column2,
tableModel.getHighestUsedRow());
shiftColumnsLeft(column2 + 1, column2 - column1 + 1);
table.getCellFormatHandler().shiftFormats(column2 + 1,
column2 - column1 + 1, Direction.Left);
table.repaintAll();
}
private void shiftColumnsRight(int startColumn) {
boolean succ = false;
int maxColumn = tableModel.getHighestUsedColumn();
int maxRow = tableModel.getHighestUsedRow();
for (int column = maxColumn; column >= startColumn; --column) {
for (int row = 0; row <= maxRow; ++row) {
GeoElement geo = RelativeCopy.getValue(app, column, row);
if (geo == null) {
continue;
}
String newLabel = GeoElementSpreadsheet
.getSpreadsheetCellName(column + 1, row);
geo.setLabel(newLabel);
succ = true;
}
}
if (succ) {
app.storeUndoInfo();
}
}
private void shiftColumnsLeft(int startColumn, int shiftAmount) {
boolean succ = false;
int maxColumn = tableModel.getHighestUsedColumn();
int maxRow = tableModel.getHighestUsedRow();
for (int column = startColumn; column <= maxColumn; ++column) {
for (int row = 0; row <= maxRow; ++row) {
GeoElement geo = RelativeCopy.getValue(app, column, row);
if (geo == null) {
continue;
}
String newLabel = GeoElementSpreadsheet
.getSpreadsheetCellName(column - shiftAmount, row);
geo.setLabel(newLabel);
succ = true;
}
}
if (succ) {
app.storeUndoInfo();
}
}
/**
* @param row1
* minimum selected row
* @param row2
* maximum selected row
* @param insertAbove
* true = insert above row1, false = insert below row2
*/
public void insertRow(int row1, int row2, boolean insertAbove) {
if (insertAbove) {
shiftRowsDown(row1);
table.getCellFormatHandler().shiftFormats(row1, 1, Direction.Down);
} else {
shiftRowsDown(row2 + 1);
table.getCellFormatHandler().shiftFormats(row2 + 1, 1,
Direction.Down);
}
table.repaintAll();
}
/**
* @param row1
* minimum selected row
* @param row2
* maximum selected row
*/
public void deleteRows(int row1, int row2) {
table.getCopyPasteCut().delete(0, row1,
tableModel.getHighestUsedColumn(), row2);
shiftRowsUp(row2 + 1, row2 - row1 + 1);
table.getCellFormatHandler().shiftFormats(row2 + 1, row2 - row1 + 1,
Direction.Up);
table.repaintAll();
}
private void shiftRowsDown(int startRow) {
int maxColumn = tableModel.getHighestUsedColumn();
int maxRow = tableModel.getHighestUsedRow();
boolean succ = false;
for (int row = maxRow; row >= startRow; --row) {
for (int column = 0; column <= maxColumn; ++column) {
GeoElement geo = RelativeCopy.getValue(app, column, row);
if (geo == null) {
continue;
}
String newLabel = GeoElementSpreadsheet
.getSpreadsheetCellName(column, row + 1);
geo.setLabel(newLabel);
succ = true;
}
}
if (succ) {
app.storeUndoInfo();
}
}
private void shiftRowsUp(int startRow, int shiftAmount) {
boolean succ = false;
int maxColumn = tableModel.getHighestUsedColumn();
int maxRow = tableModel.getHighestUsedRow();
for (int row = startRow; row <= maxRow; ++row) {
for (int column = 0; column <= maxColumn; ++column) {
GeoElement geo = RelativeCopy.getValue(app, column, row);
if (geo == null) {
continue;
}
String newLabel = GeoElementSpreadsheet
.getSpreadsheetCellName(column, row - shiftAmount);
geo.setLabel(newLabel);
succ = true;
}
}
if (succ) {
app.storeUndoInfo();
}
}
/**
* Creates an operation table.
*/
public void createOperationTable(CellRange cr) {
int r1 = cr.getMinRow();
int c1 = cr.getMinColumn();
String text = "";
GeoElementND[] geos;
GeoFunctionNVar fcn = (GeoFunctionNVar) RelativeCopy.getValue(app, c1,
r1);
for (int r = r1 + 1; r <= cr.getMaxRow(); ++r) {
for (int c = c1 + 1; c <= cr.getMaxColumn(); ++c) {
// System.out.println(AbstractGeoElementSpreadsheet.getSpreadsheetCellName(c,
// r) + ": " + text);
text = GeoElementSpreadsheet.getSpreadsheetCellName(c, r) + "="
+ fcn.getLabel(StringTemplate.defaultTemplate) + "(";
text += GeoElementSpreadsheet.getSpreadsheetCellName(c1, r);
text += ",";
text += GeoElementSpreadsheet.getSpreadsheetCellName(c, r1);
text += ")";
geos = app.getKernel().getAlgebraProcessor()
.processAlgebraCommandNoExceptions(text, false);
// geos[0].setLabel(AbstractGeoElementSpreadsheet.getSpreadsheetCellName(c,
// r));
geos[0].setAuxiliaryObject(true);
}
}
}
// Experimental ---- merging ctrl-selected cells
// private static void consolidateRangeList(ArrayList<CellRange> rangeList)
// {
//
// ArrayList<ArrayList<GPoint>> matrix = new ArrayList<ArrayList<GPoint>>();
// int minRow = rangeList.get(0).getMinRow();
// int maxRow = rangeList.get(0).getMaxRow();
// int minColumn = rangeList.get(0).getMinColumn();
// int maxColumn = rangeList.get(0).getMaxColumn();
//
// for (CellRange cr : rangeList) {
//
// minColumn = Math.min(cr.getMinColumn(), minColumn);
// maxColumn = Math.max(cr.getMaxColumn(), maxColumn);
// minRow = Math.min(cr.getMinRow(), minRow);
// maxRow = Math.max(cr.getMaxRow(), maxRow);
//
// // create matrix of cells from all ranges in the list
// for (int col = cr.getMinColumn(); col <= cr.getMaxColumn(); col++) {
//
// // add columns from this cell range to the matrix
// if (matrix.get(col) == null) {
// matrix.add(col, new ArrayList<GPoint>());
// matrix.get(col).add(
// new GPoint(cr.getMinColumn(), cr.getMaxColumn()));
// } else {
// // Point p = matrix.get(col).get(1);
// // if(cr.getMinColumn()>)
// // insertPoint(matrix, new Point(new
// // Point(cr.getMinColumn(),cr.getMaxColumn())));
// }
// }
//
// // convert our matrix to a CellRange list
// for (int col = minColumn; col <= maxColumn; col++) {
// if (matrix.contains(col)) {
// // ????
// }
// }
// }
// }
public String getCellRangeString(CellRange range) {
return getCellRangeString(range, true);
}
public String getCellRangeString(CellRange range,
boolean onlyFirstRowColumn) {
String s = "";
if (range.isColumn()) {
s = loc.getCommand("Column") + " " + GeoElementSpreadsheet
.getSpreadsheetColumnName(range.getMinColumn());
if (!onlyFirstRowColumn && !range.is1D()) {
s += " : " + loc.getCommand("Column") + " "
+ GeoElementSpreadsheet
.getSpreadsheetColumnName(range.getMaxColumn());
}
} else if (range.isRow()) {
s = loc.getCommand("Row") + " " + (range.getMinRow() + 1);
if (!onlyFirstRowColumn && !range.is1D()) {
s += " : " + loc.getCommand("Row") + " "
+ (range.getMaxRow() + 1);
}
} else {
s = GeoElementSpreadsheet.getSpreadsheetCellName(
range.getMinColumn(), range.getMinRow());
s += ":";
s += GeoElementSpreadsheet.getSpreadsheetCellName(
range.getMaxColumn(), range.getMaxRow());
}
return s;
}
public String getCellRangeString(ArrayList<CellRange> list) {
// if (list == null) {
// return "";
// }
StringBuilder sb = new StringBuilder();
for (CellRange cr : list) {
// cr.debug();
sb.append(getCellRangeString(cr, false));
sb.append(", ");
}
sb.deleteCharAt(sb.lastIndexOf(", "));
return sb.toString();
}
}