+ /**
+ * Get the currently-selected column number. 0 is the left-most column.
+ *
+ * @return the selected column number
+ */
+ public int getSelectedColumnNumber() {
+ return selectedColumn;
+ }
+
+ /**
+ * Set the currently-selected column number. 0 is the left-most column.
+ *
+ * @param column the column number to select
+ */
+ public void setSelectedColumnNumber(final int column) {
+ if ((column < 0) || (column > columns.size() - 1)) {
+ throw new IndexOutOfBoundsException("Column count is " +
+ columns.size() + ", requested index " + column);
+ }
+ selectedColumn = column;
+ activate(columns.get(selectedColumn).get(selectedRow));
+ alignGrid(true);
+ }
+
+ /**
+ * Get the currently-selected row number. 0 is the top-most row.
+ *
+ * @return the selected row number
+ */
+ public int getSelectedRowNumber() {
+ return selectedRow;
+ }
+
+ /**
+ * Set the currently-selected row number. 0 is the left-most column.
+ *
+ * @param row the row number to select
+ */
+ public void setSelectedRowNumber(final int row) {
+ if ((row < 0) || (row > rows.size() - 1)) {
+ throw new IndexOutOfBoundsException("Row count is " +
+ rows.size() + ", requested index " + row);
+ }
+ selectedRow = row;
+ activate(columns.get(selectedColumn).get(selectedRow));
+ alignGrid(true);
+ }
+
+ /**
+ * Get the number of columns.
+ *
+ * @return the number of columns
+ */
+ public int getColumnCount() {
+ return columns.size();
+ }
+
+ /**
+ * Get the number of rows.
+ *
+ * @return the number of rows
+ */
+ public int getRowCount() {
+ return rows.size();
+ }
+
+ /**
+ * Align the grid so that the selected cell is fully visible.
+ *
+ * @param force if true, always move the grid as needed
+ */
+ private void alignGrid(final boolean force) {
+ boolean resetRowY = false;
+ boolean resetColumnX = false;
+
+ if (selectedColumn < left) {
+ left = selectedColumn;
+ resetColumnX = true;
+ resetRowY = true;
+ }
+ if (selectedRow < top) {
+ top = selectedRow;
+ resetColumnX = true;
+ resetRowY = true;
+ }
+
+ if (force == true) {
+ resetRowY = true;
+ resetColumnX = true;
+ } else if ((getSelectedCell().getX() + getSelectedCell().getWidth() + 1 >
+ getWidth() - 1)
+ || (columns.get(left).get(0).getX() <
+ (showRowLabels == true ? ROW_LABEL_WIDTH : 0))
+ ) {
+ resetColumnX = true;
+ resetRowY = true;
+ } else if ((getSelectedCell().getY() + getSelectedCell().getHeight() >
+ getHeight())
+ || (rows.get(top).get(0).getY() <
+ (showColumnLabels == true ? COLUMN_LABEL_HEIGHT : 0))
+ ) {
+ resetColumnX = true;
+ resetRowY = true;
+ }
+
+ if ((resetColumnX == false) && (resetRowY == false)) {
+ // Nothing to do, bail out.
+ return;
+ }
+
+ /*
+ * We start by assuming that all cells are visible, and then mark as
+ * invisible those that are outside the viewable area.
+ */
+ for (int x = 0; x < columns.size(); x++) {
+ for (int y = 0; y < rows.size(); y++) {
+ Cell cell = rows.get(y).get(x);
+ cell.setVisible(true);
+
+ // Special case: mouse double-clicks can lead to
+ // multiple cells in editing mode. Only allow a cell
+ // to remain editing if it is fact the active widget.
+ if (cell.isEditing && !cell.isActive()) {
+ cell.fieldText = cell.field.getText();
+ cell.isEditing = false;
+ cell.field.setEnabled(false);
+ }
+ }
+ }
+
+ // Adjust X locations to be visible -----------------------------------
+
+ // Determine if we need to shift left or right.
+ int leftCellX = 0;
+ if (showRowLabels == true) {
+ // For now, all row labels are 8 cells wide. TODO: make this
+ // adjustable.
+ leftCellX += ROW_LABEL_WIDTH;
+ }
+ Row row = getSelectedRow();
+ Cell selectedColumnCell = null;
+ for (int i = 0; i < row.cells.size(); i++) {
+ if (i == selectedColumn) {
+ selectedColumnCell = row.cells.get(i);
+ break;
+ }
+ leftCellX += row.cells.get(i).getWidth() + 1;
+ }
+ // There should always be a selected column.
+ assert (selectedColumnCell != null);
+
+ if (resetColumnX == true) {
+
+ // We need to adjust everything so that the selected cell is
+ // visible.
+
+ int excessWidth = leftCellX + selectedColumnCell.getWidth() + 1 - getWidth();
+ if (excessWidth > 0) {
+ leftCellX -= excessWidth;
+ }
+ if (leftCellX < 0) {
+ if (showRowLabels == true) {
+ leftCellX = ROW_LABEL_WIDTH;
+ } else {
+ leftCellX = 0;
+ }
+ }
+
+ /*
+ * leftCellX now contains the basic left offset necessary to draw
+ * the cells such that the selected cell (column) is fully
+ * visible within this widget's given width. Or, if the widget
+ * is too narrow to display the full cell, leftCellX is 0 or
+ * ROW_LABEL_WIDTH.
+ *
+ * Now reset all of the X positions of the other cells so that
+ * the selected cell X is leftCellX.
+ */
+ for (int y = 0; y < rows.size(); y++) {
+ // All cells to the left of selected cell.
+ int newCellX = leftCellX;
+ left = selectedColumn - 1;
+ for (int x = selectedColumn - 1; x >= 0; x--) {
+ newCellX -= rows.get(y).get(x).getWidth() + 1;
+ rows.get(y).get(x).setX(newCellX);
+ if (newCellX >= (showRowLabels ? ROW_LABEL_WIDTH : 0)) {
+ if ((rows.get(y).get(0).getY() < (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0))
+ || (rows.get(y).get(0).getY() >= getHeight())
+ ) {
+ // This row isn't visible.
+ rows.get(y).get(x).setVisible(false);
+ } else {
+ rows.get(y).get(x).setVisible(true);
+ }
+ left--;
+ } else {
+ // This cell won't be visible.
+ rows.get(y).get(x).setVisible(false);
+ }
+ }
+ left++;
+
+ // Selected cell.
+ rows.get(y).get(selectedColumn).setX(leftCellX);
+ if ((rows.get(y).get(0).getY() < (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0))
+ || (rows.get(y).get(0).getY() >= getHeight())
+ ) {
+ // This row isn't visible.
+ rows.get(y).get(selectedColumn).setVisible(false);
+ } else {
+ rows.get(y).get(selectedColumn).setVisible(true);
+ }
+
+ assert (rows.get(y).get(left).getX() >= 0);
+ assert (rows.get(y).get(left).getX() + rows.get(y).get(left).getWidth() >= (showRowLabels ? ROW_LABEL_WIDTH : 0));
+
+ // All cells to the right of selected cell.
+ newCellX = leftCellX + selectedColumnCell.getWidth() + 1;
+ for (int x = selectedColumn + 1; x < columns.size(); x++) {
+ rows.get(y).get(x).setX(newCellX);
+ if (newCellX <= getWidth()) {
+ if ((rows.get(y).get(0).getY() < (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0))
+ || (rows.get(y).get(0).getY() >= getHeight())
+ ) {
+ // This row isn't visible.
+ rows.get(y).get(x).setVisible(false);
+ } else {
+ rows.get(y).get(x).setVisible(true);
+ }
+ } else {
+ // This cell won't be visible.
+ rows.get(y).get(x).setVisible(false);
+ }
+ newCellX += rows.get(y).get(x).getWidth() + 1;
+ }
+ }
+
+ } // if (resetColumnX == true)
+
+ // Adjust Y locations to be visible -----------------------------------
+ // The same logic as above, but applied to the column Y.
+
+ // Determine if we need to shift up or down.
+ int topCellY = 0;
+ if (showColumnLabels == true) {
+ // For now, all column labels are 1 cell high. TODO: make this
+ // adjustable.
+ topCellY += 1;
+ }
+ Column column = getSelectedColumn();
+ Cell selectedRowCell = null;
+ for (int i = 0; i < column.cells.size(); i++) {
+ if (i == selectedRow) {
+ selectedRowCell = column.cells.get(i);
+ break;
+ }
+ topCellY += column.cells.get(i).getHeight();
+ // TODO: if a border is selected, add 1 to topCellY.
+ }
+ // There should always be a selected row.
+ assert (selectedRowCell != null);
+
+ if (resetRowY == true) {
+
+ // We need to adjust everything so that the selected cell is
+ // visible.
+
+ int excessHeight = topCellY + selectedRowCell.getHeight() - getHeight() - 1;
+ if (showColumnLabels == true) {
+ excessHeight += COLUMN_LABEL_HEIGHT;
+ }
+ if (excessHeight > 0) {
+ topCellY -= excessHeight;
+ }
+ if (topCellY < 0) {
+ if (showColumnLabels == true) {
+ topCellY = COLUMN_LABEL_HEIGHT;
+ } else {
+ topCellY = 0;
+ }
+ }
+
+ /*
+ * topCellY now contains the basic top offset necessary to draw
+ * the cells such that the selected cell (row) is fully visible
+ * within this widget's given height. Or, if the widget is too
+ * short to display the full cell, topCellY is 0 or
+ * COLUMN_LABEL_HEIGHT.
+ *
+ * Now reset all of the Y positions of the other cells so that
+ * the selected cell Y is topCellY.
+ */
+ for (int x = 0; x < columns.size(); x++) {
+ // All cells above the selected cell.
+ int newCellY = topCellY;
+ top = selectedRow - 1;
+ for (int y = selectedRow - 1; y >= 0; y--) {
+ newCellY -= columns.get(x).get(y).getHeight();
+ columns.get(x).get(y).setY(newCellY);
+ if (newCellY >= (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0)) {
+ if ((columns.get(x).get(0).getX() < (showRowLabels ? ROW_LABEL_WIDTH : 0))
+ || (columns.get(x).get(0).getX() >= getWidth())
+ ) {
+ // This column isn't visible.
+ columns.get(x).get(y).setVisible(false);
+ } else {
+ columns.get(x).get(y).setVisible(true);
+ }
+ top--;
+ } else {
+ // This cell won't be visible.
+ columns.get(x).get(y).setVisible(false);
+ }
+ }
+ top++;
+
+ // Selected cell.
+ columns.get(x).get(selectedRow).setY(topCellY);
+ if ((columns.get(x).get(0).getX() < (showRowLabels ? ROW_LABEL_WIDTH : 0))
+ || (columns.get(x).get(0).getX() >= getWidth())
+ ) {
+ // This column isn't visible.
+ columns.get(x).get(selectedRow).setVisible(false);
+ } else {
+ columns.get(x).get(selectedRow).setVisible(true);
+ }
+
+ assert (columns.get(x).get(top).getY() >= 0);
+ assert (columns.get(x).get(top).getY() + columns.get(x).get(top).getHeight() >= (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0));
+
+ // All cells below the selected cell.
+ newCellY = topCellY + selectedRowCell.getHeight();
+ for (int y = selectedRow + 1; y < rows.size(); y++) {
+ columns.get(x).get(y).setY(newCellY);
+ if (newCellY <= getHeight()) {
+ if ((columns.get(x).get(0).getX() < (showRowLabels ? ROW_LABEL_WIDTH : 0))
+ || (columns.get(x).get(0).getX() >= getWidth())
+ ) {
+ // This column isn't visible.
+ columns.get(x).get(y).setVisible(false);
+ } else {
+ columns.get(x).get(y).setVisible(true);
+ }
+ } else {
+ // This cell won't be visible.
+ columns.get(x).get(y).setVisible(false);
+ }
+ newCellY += columns.get(x).get(y).getHeight();
+ }
+ }
+
+ } // if (resetRowY == true)
+
+ }
+
+ /**
+ * Save contents to file.
+ *
+ * @param filename file to save to
+ * @throws IOException if a java.io operation throws
+ */
+ public void saveToFilename(final String filename) throws IOException {
+ // TODO
+ }
+