/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program 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.
*/
package org.geogebra.common.kernel.statistics;
import java.util.ArrayList;
import java.util.Iterator;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.gui.view.spreadsheet.CellRange;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.algos.Algos;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoElementSpreadsheet;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoListForCellRange;
/**
* Algorithm to create a GeoList with GeoElement objects of a given range in
* GeoGebra's spreadsheet. For example, CellRange[A1, B2] (or A1:B2) returns the
* list {A1, B1, A2, B2}.
*
* @author Markus Hohenwarter
* @date 29.06.2008
*/
public class AlgoCellRange extends AlgoElement {
private GeoListForCellRange geoList; // output list of range
private String startCell, endCell; // input cells
private String toStringOutput;
private CellRange cellRange;
private ArrayList<GeoElement> listItems;
private GPoint startCoords, endCoords;
/**
* Creates an algorithm that produces a list of GeoElements for a range of
* cells in the spreadsheet.
*
* @param startCell
* e.g. A1
* @param endCell
* e.g. B2
*/
public AlgoCellRange(Construction cons, String label, String startCell,
String endCell) {
super(cons);
this.startCell = startCell;
this.endCell = endCell;
setInputOutput();
geoList.setLabel(label);
}
@Override
public Algos getClassName() {
// rather than writing <command name="CellRange"> <input a0="B2" a1="B3"
// a2="B4" etc />
// write an expression: <expression label="list1" exp="A1:A3" />
return Algos.Expression;
}
@Override
public void remove() {
if (removed) {
return;
}
// don't remove if there is some child
if (geoList.hasChildren()) {
return;
}
// remove this from item update sets
for (GeoElement geo : listItems) {
geo.removeFromUpdateSets(this);
}
super.remove();
cons.getApplication().getSpreadsheetTableModel().getCellRangeManager()
.unregisterCellRangeListenerAlgo(this);
clearGeoList();
}
private void clearGeoList() {
// remove this algorithm as cell range user to allow renaming again
for (int i = 0; i < geoList.size(); i++) {
geoList.get(i).removeCellRangeUser();
}
geoList.clear();
}
/**
* update list (add/remove geo)
*
* @param geo
* geo to add/remove
* @param isRemoveAction
* true if remove, false if add
*/
public void updateList(GeoElement geo, boolean isRemoveAction) {
if (listItems.contains(geo)) {
// exit if geo is already in the list
if (!isRemoveAction) {
return;
}
listItems.remove(geo);
} else {
listItems = initCellRangeList(startCoords, endCoords);
}
updateList();
update();
geoList.updateRepaint();
}
private void updateList() {
geoList.clear();
for (GeoElement geo : listItems) {
add(geo);
}
}
private void add(GeoElement geo) {
// add to geo list
geoList.add(geo);
// add this to geo update set
geo.addToUpdateSetOnly(this);
// add to list update set to geo update set
Iterator<AlgoElement> it = geoList.getAlgoUpdateSet().getIterator();
while (it.hasNext()) {
geo.addToUpdateSetOnly(it.next());
}
}
/**
* add geo at location into the list
*
* @param geo
* element
* @param loc
* location on spreadsheet
*/
public void addToList(GeoElement geo, GPoint loc) {
// check if we just add at the end of the list
if (loc.x >= maxExistingCol && loc.y > maxExistingRow) {
maxExistingCol = loc.x;
maxExistingRow = loc.y;
addToList(geo);
} else { // recompute the list
updateList(geo, false);
}
}
private void addToList(GeoElement geo) {
listItems.add(geo);
add(geo);
geoList.updateRepaint();
}
// for AlgoElement
@Override
protected void setInputOutput() {
startCoords = GeoElementSpreadsheet
.getSpreadsheetCoordsForLabel(startCell);
endCoords = GeoElementSpreadsheet.getSpreadsheetCoordsForLabel(endCell);
toStringOutput = startCell + ":" + endCell;
cellRange = new CellRange(cons.getApplication(), startCoords.x,
startCoords.y, endCoords.x, endCoords.y);
// build list with cells in range
listItems = initCellRangeList(startCoords, endCoords);
// create dependent geoList for cells in range
geoList = new GeoListForCellRange(cons, this);
// input: size 0
// needed for XML saving only
input = new GeoElement[0];
updateList();
update();
super.setOutputLength(1);
super.setOutput(0, geoList);
setDependenciesOutputOnly();
// see this.getClassName() for better solution
// change input now for XML saving
// input = new GeoElement[2];
// input[0] = startCell;
// input[1] = endCell;
}
/**
* max column location for existing values
*/
private int maxExistingCol;
/**
* max row location for existing values in max column location
*/
private int maxExistingRow;
/**
* Builds geoList with current objects in range of spreadsheet. Renaming of
* all cells added to the geoList is turned off, otherwise the user could
* move an object out of the range by renaming it.
*
* @param startCoords
* @param endCoords
*/
private ArrayList<GeoElement> initCellRangeList(GPoint startCoords,
GPoint endCoords) {
ArrayList<GeoElement> listItems1 = new ArrayList<GeoElement>();
// check if we have valid spreadsheet coordinates
boolean validRange = startCoords != null && endCoords != null;
if (!validRange) {
return listItems1;
}
// min and max column and row of range
int minCol = Math.min(startCoords.x, endCoords.x);
int maxCol = Math.max(startCoords.x, endCoords.x);
int minRow = Math.min(startCoords.y, endCoords.y);
int maxRow = Math.max(startCoords.y, endCoords.y);
maxExistingCol = minCol - 1;
maxExistingRow = minRow - 1;
// build the list
for (int colIndex = minCol; colIndex <= maxCol; colIndex++) {
for (int rowIndex = minRow; rowIndex <= maxRow; rowIndex++) {
// get cell object for col, row
String cellLabel = GeoElementSpreadsheet
.getSpreadsheetCellName(colIndex, rowIndex);
GeoElement geo = kernel.lookupLabel(cellLabel);
// create missing object in cell range
if (geo == null || geo.isEmptySpreadsheetCell()) {
// geo = cons
// .createSpreadsheetGeoElement(startCell, cellLabel);
continue;
}
// we got the cell object, add it to the list
listItems1.add(geo);
maxExistingCol = colIndex;
maxExistingRow = rowIndex; // we want max existing row in max
// col
// make sure that this cell object cannot be renamed by the user
// renaming would move the object outside of our range
// geo.addCellRangeUser();
}
}
return listItems1;
}
public GeoList getList() {
return geoList;
}
public CellRange getCellRange() {
return cellRange;
}
@Override
public final void compute() {
// just update list
geoList.update();
}
@Override
final public String getDefinition(StringTemplate tpl) {
return toStringOutput;
}
@Override
final public String toString(StringTemplate tpl) {
return toStringOutput;
}
public GPoint[] getRectangle() {
GPoint startCoords1 = GeoElementSpreadsheet
.getSpreadsheetCoordsForLabel(startCell);
GPoint endCoords1 = GeoElementSpreadsheet
.getSpreadsheetCoordsForLabel(endCell);
GPoint[] ret = { startCoords1, endCoords1 };
return ret;
}
/**
* add algo to input items update sets
*
* @param algo
*/
public void addToItemsAlgoUpdateSets(AlgoElement algo) {
for (GeoElement geo : listItems) {
geo.addToUpdateSetOnly(algo);
}
}
public String getStart() {
return startCell;
}
public String getEnd() {
return endCell;
}
}