package org.geogebra.common.kernel.geos;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.gui.view.spreadsheet.CellFormat;
import org.geogebra.common.gui.view.spreadsheet.CellFormatInterface;
import org.geogebra.common.gui.view.spreadsheet.SpreadsheetViewInterface;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.arithmetic.FunctionalNVar;
import org.geogebra.common.main.App;
import org.geogebra.common.main.GuiManagerInterface;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
/**
* Collection of methods for handling spreadsheet cell names
*
*/
public class GeoElementSpreadsheet {
/**
* match A1, ABG1, A123 but not A0, A000, A0001 etc
*/
public static final RegExp spreadsheetPattern = RegExp
.compile("^(\\$?)([A-Z]+)(\\$?)([1-9][0-9]*)$");
/** regex group with "$" or "" for column */
public final static int MATCH_COLUMN_$ = 1;
/** regex group for column name */
public final static int MATCH_COLUMN = 2;
/** regex group with "$" or "" for row */
public final static int MATCH_ROW_$ = 3;
/** regex group for row number */
public final static int MATCH_ROW = 4;
/**
* Converts column number to name
*
* @param column
* column number
* @return column name
*/
public static String getSpreadsheetColumnName(int column) {
int i = column + 1;
String col = "";
while (i > 0) {
col = (char) ('A' + (i - 1) % 26) + col;
i = (i - 1) / 26;
}
return col;
}
/**
* Extracts column from cell name
*
* @param label
* cell label
* @return column name
*/
public static String getSpreadsheetColumnName(String label) {
MatchResult matcher = spreadsheetPattern.exec(label);
if (matcher == null) {
return null;
}
return matcher.getGroup(MATCH_COLUMN);
}
/**
* Converts coordinates into cell name
*
* @param column
* cell column
* @param row
* cell row
* @return cell name
* @author Cong Liu
*/
public static String getSpreadsheetCellName(int column, int row) {
if (column >= Kernel.MAX_SPREADSHEET_COLUMNS_DESKTOP
|| row >= Kernel.MAX_SPREADSHEET_ROWS_DESKTOP || column < 0
|| row < 0) {
return null;
}
return getSpreadsheetColumnName(column) + (row + 1);
}
/**
* Determines spreadsheet row and column indices for a given cell name (e.g.
* "B3" sets column = 1 and row = 2. If the cell name does not match a
* possible spreadsheet cell then both row and column are returned as -1.
*
* @param cellName
* given cell name
* @return coordinates of spreadsheet cell as (column index,row index)
*/
public static GPoint spreadsheetIndices(String cellName) {
MatchResult matcher = spreadsheetPattern.exec(cellName);
// return (-1,-1) if not a spreadsheet cell name
return new GPoint(getSpreadsheetColumn(matcher),
getSpreadsheetRow(matcher));
}
/**
* Checks whether geo has valid cell name. We use getLabel() rather than
* getLabelSimple() here because of labels like $A$1
*
* @param geo
* geo
* @return true if label is valid cell name
* @author Michael Borcherds
*/
public static boolean hasSpreadsheetLabel(GeoElement geo) {
return isSpreadsheetLabel(geo.getLabel(StringTemplate.defaultTemplate));
}
/**
* Checks whether str is valid cell name
*
* @param str
* label
* @return true if label is valid cell name
* @author Michael Borcherds
*/
public static boolean isSpreadsheetLabel(String str) {
if (str == null) {
return false;
}
MatchResult matcher = spreadsheetPattern.exec(str);
if (matcher == null) {
return false;
}
// check not outside range, eg A10000
if (getSpreadsheetColumn(matcher) == -1
|| getSpreadsheetRow(matcher) == -1) {
return false;
}
return true;
}
/**
* @param matcher
* matcher
* @return column matching the matcher
*/
public static int getSpreadsheetColumn(MatchResult matcher) {
// if (!matcher.matches())
// return -1;
if (matcher == null) {
return -1;
}
String s = matcher.getGroup(MATCH_COLUMN);
int column = 0;
while (s.length() > 0) {
column *= 26;
column += s.charAt(0) - 'A' + 1;
s = s.substring(1);
}
if (column > Kernel.MAX_SPREADSHEET_COLUMNS_DESKTOP) {
return -1;
}
// Application.debug(column);
return column - 1;
}
/**
* Returns row number based on matcher, which was obtained using
* spreadsheetPattern
*
* @author Cong Liu
* @param matcher
* matcher
* @return row number
*/
public static int getSpreadsheetRow(MatchResult matcher) {
if (matcher == null) {
return -1;
}
int ret = -1;
try {
String s = matcher.getGroup(MATCH_ROW);
ret = Integer.parseInt(s) - 1;
} catch (Exception e) {
// eg number is bigger than MAXINT
return -1;
}
if (ret + 1 > Kernel.MAX_SPREADSHEET_ROWS_DESKTOP) {
return -1;
}
return ret;
}
/**
* Returns a point with the spreadsheet coordinates of the given inputLabel.
* Note that this can also be used for names that include $ signs like
* "$A1".
*
* @param inputLabel
* label of spreadsheet cell
* @return spreadsheet coordinates as (column index,row index); null for
* non-spreadsheet names
*/
public static GPoint getSpreadsheetCoordsForLabel(String inputLabel) {
// we need to also support wrapped GeoElements like
// $A4 that are implemented as dependent geos (using ExpressionNode)
GPoint p = spreadsheetIndices(inputLabel);
if (p.x >= 0 && p.y >= 0) {
return p;
}
return null;
}
private static StringBuilder sb;
/**
* used to set a cell to another geo used by FillCells[] etc
*
* @param app
* application
* @param row
* destination row
* @param col
* destination column
* @param cellGeo
* source element
*/
public void setSpreadsheetCell(App app, int row, int col,
GeoElement cellGeo) {
String cellName = getSpreadsheetCellName(col, row);
if (sb == null) {
sb = new StringBuilder();
} else {
sb.setLength(0);
}
sb.append(cellName);
if (cellGeo instanceof FunctionalNVar) {
sb.append("(");
sb.append(((FunctionalNVar) cellGeo)
.getVarString(StringTemplate.defaultTemplate));
sb.append(")");
}
// getLabel() returns algoParent.getCommandDescription() or
// toValueString()
// if there's no label (eg {1,2})
String label = cellGeo.getLabel(StringTemplate.defaultTemplate);
// need an = for B3=B4
// need a : for B2:x^2 + y^2 = 2
if (label.indexOf('=') == -1) {
sb.append('=');
} else {
sb.append(':');
}
sb.append(label);
// we only sometimes need (x), eg
// B2(x)=f(x)
// B2(x)=x^2
if (cellGeo instanceof FunctionalNVar && cellGeo.isLabelSet()) {
sb.append("(");
sb.append(((FunctionalNVar) cellGeo)
.getVarString(StringTemplate.defaultTemplate));
sb.append(")");
}
// Application.debug(sb.toString());
app.getKernel().getAlgebraProcessor()
.processAlgebraCommand(sb.toString(), false);
GeoElement cell = app.getKernel().lookupLabel(cellName);
if (cell != null) {
(cell).setVisualStyle(cellGeo);
(cell).setAuxiliaryObject(true);
}
}
/**
* @param label
* label
* @param cons
* construction
* @return created geo or null
*/
public static GeoElement autoCreate(String label, Construction cons) {
if (cons.getKernel().isSilentMode()) {
return null;
}
MatchResult cellNameMatcher = spreadsheetPattern.exec(label);
if (cellNameMatcher != null) {
String col = cellNameMatcher.getGroup(MATCH_COLUMN);
int row = Integer.parseInt(cellNameMatcher.getGroup(MATCH_ROW));
// try to get neighbouring cell for object type look above
GeoElement neighbourCell = cons.geoTableVarLookup(col + (row - 1));
if (neighbourCell == null) {
neighbourCell = cons.geoTableVarLookup(col + (row + 1));
}
String label1 = col + row;
return cons.createSpreadsheetGeoElement(neighbourCell, label1);
}
return null;
}
/**
* copies the background color from the cell to the object when an object is
* created (or renamed)
*
* @param geo
* to check
*/
public static void setBackgroundColor(GeoElement geo) {
if (geo.getKernel().getConstruction().isFileLoading()) {
return;
}
GuiManagerInterface guiManager = geo.getKernel().getApplication()
.getGuiManager();
if (guiManager == null || !guiManager.hasSpreadsheetView()) {
// no spreadsheet
return;
}
String label = geo.getLabelSimple();
if (GeoElementSpreadsheet.isSpreadsheetLabel(label)) {
GPoint coords = GeoElementSpreadsheet.spreadsheetIndices(label);
SpreadsheetViewInterface spreadsheet = guiManager
.getSpreadsheetView();
CellFormatInterface formatHandler = spreadsheet
.getSpreadsheetTable().getCellFormatHandler();
Object c = formatHandler.getCellFormat(coords.x, coords.y,
CellFormat.FORMAT_BGCOLOR);
if (c instanceof GColor) {
geo.setBackgroundColor((GColor) c);
}
}
}
}