package org.geogebra.common.gui.view.spreadsheet; import java.util.ArrayList; import org.geogebra.common.awt.GPoint; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoElementSpreadsheet; import org.geogebra.common.main.App; import org.geogebra.common.main.SpreadsheetTableModel; import org.geogebra.common.plugin.GeoClass; import org.geogebra.common.util.debug.Log; /** * Utility class for spreadsheet cell ranges. * * A cell range is any rectangular block of cells defined by the column and row * index values of two diagonal corner cells. One corner is designated as the * anchor cell. * * Rows and columns are defined using index values of -1 as follows. row: * minColumn and maxColumn = -1 column: minRow and maxRow = -1 * * * @author George Sturr, 2010-1-23 */ final public class CellRange { private int minColumn = -1; private int minRow = -1; private int maxColumn = -1; private int maxRow = -1; private int anchorColumn = -1; private int anchorRow = -1; // private MyTable table; App app; private SpreadsheetTableModel tableModel; /** * Constructs an empty CellRange * * @param app */ public CellRange(App app) { this.tableModel = app.getSpreadsheetTableModel(); this.app = app; } /** * Constructs a CellRange using all row/column indices * * @param app * @param anchorColumn * @param anchorRow * @param minColumn * @param minRow * @param maxColumn * @param maxRow */ public CellRange(App app, int anchorColumn, int anchorRow, int minColumn, int minRow, int maxColumn, int maxRow) { this.tableModel = app.getSpreadsheetTableModel(); this.app = app; this.anchorColumn = anchorColumn; this.anchorRow = anchorRow; this.minColumn = minColumn; this.minRow = minRow; this.maxColumn = maxColumn; this.maxRow = maxRow; } /** * Constructs a CellRange from an anchor and opposite corner * * @param app * @param anchorColumn * @param anchorRow * @param col2 * @param row2 */ public CellRange(App app, int anchorColumn, int anchorRow, int col2, int row2) { this.tableModel = app.getSpreadsheetTableModel(); this.app = app; setCellRange(anchorColumn, anchorRow, col2, row2); } /** * Constructs a CellRange for single cell * * @param app * @param anchorColumn * @param anchorRow */ public CellRange(App app, int anchorColumn, int anchorRow) { this.tableModel = app.getSpreadsheetTableModel(); this.app = app; setCellRange(anchorColumn, anchorRow, anchorColumn, anchorRow); } // TODO Constructor with string parameter, e.g. CellRange("A1:B10") /** Set cell range for a single cell */ public void setCellRange(int anchorColumn, int anchorRow) { setCellRange(anchorColumn, anchorRow, anchorColumn, anchorRow); } /** * Set a cell range using diagonal corner cells. Can be any two diagonal * corners in either order. */ public void setCellRange(int anchorColumn, int anchorRow, int col2, int row2) { minColumn = Math.min(anchorColumn, col2); maxColumn = Math.max(anchorColumn, col2); minRow = Math.min(anchorRow, row2); maxRow = Math.max(anchorRow, row2); this.anchorColumn = anchorColumn; this.anchorRow = anchorRow; } public int getAnchorColumn() { return anchorColumn; } public int getAnchorRow() { return anchorRow; } public int getMinColumn() { return minColumn; } public int getMinRow() { return minRow; } public int getMaxColumn() { return maxColumn; } public int getMaxRow() { return maxRow; } public boolean isSingleCell() { return ((maxColumn - minColumn == 0) && (maxRow - minRow == 0)); } public boolean isColumn() { return (anchorRow == -1); } public boolean isRow() { return (anchorColumn == -1); } public int[] getActualDimensions() { int[] d = new int[2]; CellRange cr = getActualRange(); d[0] = cr.maxRow - cr.minRow + 1; d[1] = cr.maxColumn - cr.minColumn + 1; return d; } // TODO -- refactor this name, should mean has either exactly 2 rows or // exactly 2 columns /** * @return true if cell range is 2xn or nx2 */ public boolean is2D() { return (maxColumn - minColumn == 1) || (maxRow - minRow == 1); } /** * @return true if cell range is 3xn or nx3 */ public boolean is3D() { return (maxColumn - minColumn == 2) || (maxRow - minRow == 2); } /** * @return true if cell range is 1xn, nx1, a row or a column */ public boolean is1D() { return ((maxColumn - minColumn == 0) || (maxRow - minRow == 0)); } /** * @return true if cell range is part of a row, but bigger than one cell */ public boolean isPartialRow() { return !isSingleCell() && !isRow() && (maxRow - minRow == 0); } public boolean isPartialColumn() { return !isSingleCell() && !isColumn() && (maxColumn - minColumn == 0); } /** isEmpty = cell range contains no geos */ public boolean isEmpty() { return toGeoList().size() == 0; } /** isEmptyRange = the range contains no cells */ public boolean isEmptyRange() { return (minColumn == -1 && maxColumn == -1 && minRow == -1 && maxRow == -1); } /** * Returns true if all non-empty cells in the given range are GeoPoint */ public boolean isPointList() { for (int col = minColumn; col <= maxColumn; ++col) { for (int row = minRow; row <= maxRow; ++row) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo != null && !geo.isGeoPoint()) { return false; } } } return true; } /** * Returns a new cell range that holds the actual cell range, e.g. * (-1,1,-1,4) ---> (0,1,100,4) */ public CellRange getActualRange() { CellRange adjustedCellRange = duplicate(); if (minRow == -1 && maxRow == -1 && minColumn != -1) { adjustedCellRange.minRow = 0; adjustedCellRange.maxRow = tableModel.getRowCount() - 1; } if (minColumn == -1 && maxColumn == -1 && minRow != -1) { adjustedCellRange.minColumn = 0; adjustedCellRange.maxColumn = tableModel.getColumnCount() - 1; } return adjustedCellRange; } /** * Sets the corners of a row or column to the actual cell range e.g. * (-1,1,-1,4) ---> (0,1,100,4) */ public void setActualRange() { if (minRow == -1 && maxRow == -1 && minColumn == -1 && maxColumn == -1) { return; } if (minRow == -1 && maxRow == -1) { minRow = 0; maxRow = tableModel.getRowCount() - 1; } if (minColumn == -1 && maxColumn == -1) { minColumn = 0; maxColumn = tableModel.getColumnCount() - 1; } } public int getWidth() { return maxColumn - minColumn + 1; } public int getHeight() { return maxRow - minRow + 1; } /** * ArrayList of all geos found in the cell range */ public ArrayList<GeoElement> toGeoList() { ArrayList<GeoElement> list = new ArrayList<GeoElement>(); for (int col = minColumn; col <= maxColumn; ++col) { for (int row = minRow; row <= maxRow; ++row) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo != null) { list.add(geo); } } } return list; } /** * ArrayList of labels for each geo found in the cell range */ public ArrayList<String> toGeoLabelList(boolean scanByColumn, boolean copyByValue) { ArrayList<String> list = new ArrayList<String>(); if (scanByColumn) { for (int col = minColumn; col <= maxColumn; ++col) { for (int row = minRow; row <= maxRow; ++row) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo != null) { if (copyByValue) { list.add(geo.getValueForInputBar()); } else { list.add(geo .getLabel(StringTemplate.defaultTemplate)); } } } } } else { for (int row = minRow; row <= maxRow; ++row) { for (int col = minColumn; col <= maxColumn; ++col) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo != null) { if (copyByValue) { list.add(geo.getValueForInputBar()); } else { list.add(geo .getLabel(StringTemplate.defaultTemplate)); } } } } } return list; } public String getLabel() { return GeoElementSpreadsheet.getSpreadsheetCellName(minColumn, minRow) + ":" + GeoElementSpreadsheet.getSpreadsheetCellName(maxColumn, maxRow); } /** * ArrayList of geo value string for each geo found in the given cell range */ public ArrayList<String> toGeoValueList(boolean scanByColumn) { ArrayList<String> list = new ArrayList<String>(); CellRange cr = getActualRange(); if (scanByColumn) { for (int col = cr.minColumn; col <= cr.maxColumn; ++col) { for (int row = cr.minRow; row <= cr.maxRow; ++row) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo != null) { list.add(geo .toValueString(StringTemplate.defaultTemplate)); } } } } else { for (int row = cr.minRow; row <= cr.maxRow; ++row) { for (int col = cr.minColumn; col <= cr.maxColumn; ++col) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo != null) { list.add(geo .toValueString(StringTemplate.defaultTemplate)); } } } } return list; } public ArrayList<CellRange> toPartialColumnList() { ArrayList<CellRange> list = new ArrayList<CellRange>(); if (isColumn()) { for (int col = minColumn; col <= maxColumn; col++) { CellRange cr = new CellRange(app, col, -1, col, 0, col, maxRow); list.add(cr); // cr.debug(); } } else { for (int col = minColumn; col <= maxColumn; col++) { list.add(new CellRange(app, col, minRow, col, maxRow)); } } return list; } public ArrayList<CellRange> toPartialRowList() { ArrayList<CellRange> list = new ArrayList<CellRange>(); if (isRow()) { for (int row = minRow; row <= maxRow; row++) { list.add(new CellRange(app, 0, row, -1, row, maxColumn, row)); } } else { for (int row = minRow; row <= maxRow; row++) { list.add(new CellRange(app, minColumn, row, maxColumn, row)); } } return list; } /** * ArrayList of all cells found in the cell range */ public ArrayList<GPoint> toCellList(boolean scanByColumn) { ArrayList<GPoint> list = new ArrayList<GPoint>(); if (scanByColumn) { for (int col = minColumn; col <= maxColumn; ++col) { for (int row = minRow; row <= maxRow; ++row) { list.add(new GPoint(col, row)); } } } else { for (int row = minRow; row <= maxRow; ++row) { for (int col = minColumn; col <= maxColumn; ++col) { list.add(new GPoint(col, row)); } } } return list; } public boolean hasSameAnchor(CellRange cr) { return (cr.anchorRow == anchorRow) && (cr.anchorColumn == anchorColumn); } /** Returns true if at least one cell is empty (has no geo) */ public boolean hasEmptyCells() { boolean hasEmptyCells = false; for (int col = minColumn; col <= maxColumn; ++col) { for (int row = minRow; row <= maxRow; ++row) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo == null) { return true; } } } return hasEmptyCells; } /** * Returns the number of GeoElements of a given GeoClass type contained in * this cell range * * @param geoClass * the GeoClass type to count. If null, then all GeoElements are * counted * @return */ public int getGeoCount(GeoClass geoClass) { int count = 0; if (geoClass != null) { for (int col = getMinColumn(); col <= getMaxColumn(); ++col) { for (int row = getMinRow(); row <= getMaxRow(); ++row) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo != null && geo.getGeoClassType() == geoClass) { ++count; } } } } else { for (int col = getMinColumn(); col <= getMaxColumn(); ++col) { for (int row = getMinRow(); row <= getMaxRow(); ++row) { if (RelativeCopy.getValue(app, col, row) != null) { ++count; } } } } return count; } /** * @param geoClass * @return true if this CellRange contains a GeoElement of the given * GeoClass type */ public boolean containsGeoClass(GeoClass geoClass) { for (int col = getMinColumn(); col <= getMaxColumn(); ++col) { for (int row = getMinRow(); row <= getMaxRow(); ++row) { GeoElement geo = RelativeCopy.getValue(app, col, row); if (geo != null && geo.getGeoClassType() == geoClass) { return true; } } } return false; } /** * Returns true if the cell range has valid coordinates for this table */ public boolean isValid() { return (minRow >= -1 && minRow < Kernel.MAX_SPREADSHEET_ROWS_DESKTOP) && (maxRow >= -1 && maxRow < Kernel.MAX_SPREADSHEET_ROWS_DESKTOP) && (minColumn >= -1 && minColumn < Kernel.MAX_SPREADSHEET_COLUMNS_DESKTOP) && (maxColumn >= -1 && maxColumn < Kernel.MAX_SPREADSHEET_COLUMNS_DESKTOP); } @SuppressWarnings("all") final public CellRange duplicate() { CellRange cr = new CellRange(app); cr.anchorColumn = anchorColumn; cr.anchorRow = anchorRow; cr.minColumn = minColumn; cr.maxColumn = maxColumn; cr.minRow = minRow; cr.maxRow = maxRow; return cr; } @Override public boolean equals(Object obj) { CellRange cr; if (obj instanceof CellRange) { cr = (CellRange) obj; return (cr.minColumn == minColumn && cr.minRow == minRow && cr.maxColumn == maxColumn && cr.maxRow == maxRow && cr.anchorColumn == anchorColumn && cr.anchorRow == anchorRow); } return false; } @Override public int hashCode() { return (minColumn << 24) ^ ((maxColumn - minColumn) << 16) ^ (minRow << 8) ^ (maxRow - minRow); } /** * @param geo * @return true if the given GeoElement is a spreadsheet cell contained * inside this cell range */ public boolean contains(GeoElement geo) { return contains(geo.getSpreadsheetCoords()); } /** * @param location * @return true if the given spreadsheet cell location is contained in this * cell range */ public boolean contains(GPoint location) { if (location != null && location.x < Kernel.MAX_SPREADSHEET_COLUMNS_DESKTOP && location.y < Kernel.MAX_SPREADSHEET_ROWS_DESKTOP) { setActualRange(); return (location.y >= minRow && location.y <= maxRow && location.x >= minColumn && location.x <= maxColumn); } return false; } /** * Prints debugging information about the cell range */ public void debug() { Log.debug("-------------------------"); Log.debug("anchor cell: (" + anchorColumn + "," + anchorRow + ")"); Log.debug("corner cells: (" + minColumn + "," + minRow + ") (" + maxColumn + "," + maxRow + ")"); Log.debug("isRow: " + isRow()); Log.debug("isColumn: " + isColumn()); } }