X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTTableWidget.java;h=749b7313c9e29874cacacc431305ebf81e6d70ce;hb=HEAD;hp=c8eeb5722c10adcb5dfcf7978cfd583cb7b80032;hpb=73162e7fb88c3600da3ae99905180d86b4ab02f6;p=fanfix.git diff --git a/src/jexer/TTableWidget.java b/src/jexer/TTableWidget.java index c8eeb57..749b731 100644 --- a/src/jexer/TTableWidget.java +++ b/src/jexer/TTableWidget.java @@ -28,16 +28,20 @@ */ package jexer; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import jexer.bits.CellAttributes; +import jexer.bits.StringUtils; import jexer.event.TKeypressEvent; -import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; -import jexer.menu.TMenu; import static jexer.TKeypress.*; /** @@ -74,12 +78,16 @@ public class TTableWidget extends TWidget { DOUBLE, /** - * Thick bar: \u258C (vertical, left half block) and \u2580 - * (horizontal, upper block). + * Thick bar: \u2503 (vertical heavy) and \u2501 (horizontal heavy). */ THICK, } + /** + * If true, put a grid of numbers in the cells. + */ + private static final boolean DEBUG = false; + /** * Row label width. */ @@ -90,15 +98,20 @@ public class TTableWidget extends TWidget { */ private static final int COLUMN_LABEL_HEIGHT = 1; + /** + * Column default width. + */ + private static final int COLUMN_DEFAULT_WIDTH = 8; + /** * Extra rows to add. */ - private static final int EXTRA_ROWS = 10; + private static final int EXTRA_ROWS = (DEBUG ? 10 : 0); /** * Extra columns to add. */ - private static final int EXTRA_COLUMNS = 10 * (8 + 1); + private static final int EXTRA_COLUMNS = (DEBUG ? 3 : 0); // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- @@ -169,10 +182,15 @@ public class TTableWidget extends TWidget { */ public class Column { + /** + * X position of this column. + */ + private int x = 0; + /** * Width of column. */ - private int width = 8; + private int width = COLUMN_DEFAULT_WIDTH; /** * The cells of this column. @@ -196,15 +214,7 @@ public class TTableWidget extends TWidget { * "A", column 1 will be "B", column 26 will be "AA", and so on. */ Column(int col) { - StringBuilder sb = new StringBuilder(); - for (;;) { - sb.append((char) ('A' + (col % 26))); - if (col < 26) { - break; - } - col /= 26; - } - label = sb.reverse().toString(); + label = makeColumnLabel(col); } /** @@ -225,6 +235,28 @@ public class TTableWidget extends TWidget { public Cell get(final int row) { return cells.get(row); } + + /** + * Get the X position of the cells in this column. + * + * @return the position + */ + public int getX() { + return x; + } + + /** + * Set the X position of the cells in this column. + * + * @param x the position + */ + public void setX(final int x) { + for (Cell cell: cells) { + cell.setX(x); + } + this.x = x; + } + } /** @@ -232,6 +264,11 @@ public class TTableWidget extends TWidget { */ public class Row { + /** + * Y position of this row. + */ + private int y = 0; + /** * Height of row. */ @@ -279,6 +316,26 @@ public class TTableWidget extends TWidget { public Cell get(final int column) { return cells.get(column); } + /** + * Get the Y position of the cells in this column. + * + * @return the position + */ + public int getY() { + return y; + } + + /** + * Set the Y position of the cells in this column. + * + * @param y the position + */ + public void setY(final int y) { + for (Cell cell: cells) { + cell.setY(y); + } + this.y = y; + } } @@ -313,6 +370,11 @@ public class TTableWidget extends TWidget { */ private boolean isEditing = false; + /** + * If true, the cell is read-only (non-editable). + */ + private boolean readOnly = false; + /** * Text of field before editing. */ @@ -370,7 +432,7 @@ public class TTableWidget extends TWidget { // Let the table know that I was activated. ((TTableWidget) getParent()).selectedRow = row; ((TTableWidget) getParent()).selectedColumn = column; - ((TTableWidget) getParent()).alignGrid(false); + ((TTableWidget) getParent()).alignGrid(); } } @@ -388,7 +450,7 @@ public class TTableWidget extends TWidget { // Let the table know that I was activated. ((TTableWidget) getParent()).selectedRow = row; ((TTableWidget) getParent()).selectedColumn = column; - ((TTableWidget) getParent()).alignGrid(false); + ((TTableWidget) getParent()).alignGrid(); } } @@ -406,7 +468,7 @@ public class TTableWidget extends TWidget { // Let the table know that I was activated. ((TTableWidget) getParent()).selectedRow = row; ((TTableWidget) getParent()).selectedColumn = column; - ((TTableWidget) getParent()).alignGrid(false); + ((TTableWidget) getParent()).alignGrid(); } } @@ -419,16 +481,24 @@ public class TTableWidget extends TWidget { public void onKeypress(final TKeypressEvent keypress) { // System.err.println("Cell onKeypress: " + keypress); + if (readOnly) { + // Read only: do nothing. + return; + } + if (isEditing) { if (keypress.equals(kbEsc)) { // ESC cancels the edit. - field.setText(fieldText); - isEditing = false; - field.setEnabled(false); + cancelEdit(); return; } if (keypress.equals(kbEnter)) { // Enter ends editing. + + // Pass down to field first so that it can execute + // enterAction if specified. + super.onKeypress(keypress); + fieldText = field.getText(); isEditing = false; field.setEnabled(false); @@ -508,6 +578,28 @@ public class TTableWidget extends TWidget { field.setText(text); } + /** + * Cancel any pending edit. + */ + public void cancelEdit() { + // Cancel any pending edit. + if (fieldText != null) { + field.setText(fieldText); + } + isEditing = false; + field.setEnabled(false); + } + + /** + * Set an entire column of cells read-only (non-editable) or not. + * + * @param readOnly if true, the cells will be non-editable + */ + public void setReadOnly(final boolean readOnly) { + cancelEdit(); + this.readOnly = readOnly; + } + } // ------------------------------------------------------------------------ @@ -522,56 +614,82 @@ public class TTableWidget extends TWidget { * @param y row relative to parent * @param width width of widget * @param height height of widget + * @param gridColumns number of columns in grid + * @param gridRows number of rows in grid */ public TTableWidget(final TWidget parent, final int x, final int y, - final int width, final int height) { + final int width, final int height, final int gridColumns, + final int gridRows) { super(parent, x, y, width, height); + /* + System.err.println("gridColumns " + gridColumns + + " gridRows " + gridRows); + */ + + if (gridColumns < 1) { + throw new IllegalArgumentException("Column count cannot be less " + + "than 1"); + } + if (gridRows < 1) { + throw new IllegalArgumentException("Row count cannot be less " + + "than 1"); + } + // Initialize the starting row and column. rows.add(new Row(0)); columns.add(new Column(0)); + assert (rows.get(0).height == 1); // Place a grid of cells that fit in this space. - int row = 0; - for (int i = 0; i < height + EXTRA_ROWS; i += rows.get(0).height) { - int column = 0; - for (int j = 0; j < width + EXTRA_COLUMNS; - j += columns.get(0).width) { - - Cell cell = new Cell(this, j, i, columns.get(0).width, - rows.get(0).height, column, row); - - cell.setText("" + row + " " + column); + for (int row = 0; row < gridRows; row++) { + for (int column = 0; column < gridColumns; column++) { + Cell cell = new Cell(this, 0, 0, COLUMN_DEFAULT_WIDTH, 1, + column, row); + + if (DEBUG) { + // For debugging: set a grid of cell index labels. + cell.setText("" + row + " " + column); + } rows.get(row).add(cell); columns.get(column).add(cell); - if ((i == 0) && - (j + columns.get(0).width < width + EXTRA_COLUMNS) - ) { + + if (columns.size() < gridColumns) { columns.add(new Column(column + 1)); } - column++; } - if (i + rows.get(0).height < height + EXTRA_ROWS) { + if (row < gridRows - 1) { rows.add(new Row(row + 1)); } - row++; + } + for (int i = 0; i < rows.size(); i++) { + rows.get(i).setY(i + (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0)); + } + for (int j = 0; j < columns.size(); j++) { + columns.get(j).setX((j * (COLUMN_DEFAULT_WIDTH + 1)) + + (showRowLabels ? ROW_LABEL_WIDTH : 0)); } activate(columns.get(selectedColumn).get(selectedRow)); - alignGrid(true); - - // Set the menu to match the flags. - getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_ROW_LABELS). - setChecked(showRowLabels); - getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_COLUMN_LABELS). - setChecked(showColumnLabels); - getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_HIGHLIGHT_ROW). - setChecked(highlightRow); - getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_HIGHLIGHT_COLUMN). - setChecked(highlightColumn); + alignGrid(); + } + /** + * Public constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width width of widget + * @param height height of widget + */ + public TTableWidget(final TWidget parent, final int x, final int y, + final int width, final int height) { + this(parent, x, y, width, height, + width / (COLUMN_DEFAULT_WIDTH + 1) + EXTRA_COLUMNS, + height + EXTRA_ROWS); } // ------------------------------------------------------------------------ @@ -624,8 +742,6 @@ public class TTableWidget extends TWidget { return; } - boolean forceGridAlign = false; - if (keypress.equals(kbLeft)) { // Left if (selectedColumn > 0) { @@ -654,12 +770,10 @@ public class TTableWidget extends TWidget { // Home - leftmost column selectedColumn = 0; activate(columns.get(selectedColumn).get(selectedRow)); - forceGridAlign = true; } else if (keypress.equals(kbEnd)) { // End - rightmost column selectedColumn = columns.size() - 1; activate(columns.get(selectedColumn).get(selectedRow)); - forceGridAlign = true; } else if (keypress.equals(kbPgUp)) { // PgUp - Treat like multiple up for (int i = 0; i < getHeight() - 2; i++) { @@ -668,7 +782,6 @@ public class TTableWidget extends TWidget { } } activate(columns.get(selectedColumn).get(selectedRow)); - forceGridAlign = true; } else if (keypress.equals(kbPgDn)) { // PgDn - Treat like multiple up for (int i = 0; i < getHeight() - 2; i++) { @@ -677,21 +790,18 @@ public class TTableWidget extends TWidget { } } activate(columns.get(selectedColumn).get(selectedRow)); - forceGridAlign = true; } else if (keypress.equals(kbCtrlHome)) { // Ctrl-Home - go to top-left selectedRow = 0; selectedColumn = 0; activate(columns.get(selectedColumn).get(selectedRow)); activate(columns.get(selectedColumn).get(selectedRow)); - forceGridAlign = true; } else if (keypress.equals(kbCtrlEnd)) { // Ctrl-End - go to bottom-right selectedRow = rows.size() - 1; selectedColumn = columns.size() - 1; activate(columns.get(selectedColumn).get(selectedRow)); activate(columns.get(selectedColumn).get(selectedRow)); - forceGridAlign = true; } else { // Pass to the Cell. super.onKeypress(keypress); @@ -699,7 +809,7 @@ public class TTableWidget extends TWidget { // We may have scrolled off screen. Reset positions as needed to // make the newly selected cell visible. - alignGrid(forceGridAlign); + alignGrid(); } /** @@ -711,83 +821,7 @@ public class TTableWidget extends TWidget { public void onResize(final TResizeEvent event) { super.onResize(event); - alignGrid(true); - } - - /** - * Handle posted menu events. - * - * @param menu menu event - */ - @Override - public void onMenu(final TMenuEvent menu) { - switch (menu.getId()) { - case TMenu.MID_TABLE_VIEW_ROW_LABELS: - showRowLabels = getApplication().getMenuItem(menu.getId()).getChecked(); - break; - case TMenu.MID_TABLE_VIEW_COLUMN_LABELS: - showColumnLabels = getApplication().getMenuItem(menu.getId()).getChecked(); - break; - case TMenu.MID_TABLE_VIEW_HIGHLIGHT_ROW: - highlightRow = getApplication().getMenuItem(menu.getId()).getChecked(); - break; - case TMenu.MID_TABLE_VIEW_HIGHLIGHT_COLUMN: - highlightColumn = getApplication().getMenuItem(menu.getId()).getChecked(); - break; - case TMenu.MID_TABLE_BORDER_NONE: - case TMenu.MID_TABLE_BORDER_ALL: - case TMenu.MID_TABLE_BORDER_RIGHT: - case TMenu.MID_TABLE_BORDER_LEFT: - case TMenu.MID_TABLE_BORDER_TOP: - case TMenu.MID_TABLE_BORDER_BOTTOM: - case TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM: - case TMenu.MID_TABLE_BORDER_THICK_BOTTOM: - case TMenu.MID_TABLE_DELETE_LEFT: - case TMenu.MID_TABLE_DELETE_UP: - case TMenu.MID_TABLE_DELETE_ROW: - case TMenu.MID_TABLE_DELETE_COLUMN: - case TMenu.MID_TABLE_INSERT_LEFT: - case TMenu.MID_TABLE_INSERT_RIGHT: - case TMenu.MID_TABLE_INSERT_ABOVE: - case TMenu.MID_TABLE_INSERT_BELOW: - break; - case TMenu.MID_TABLE_COLUMN_NARROW: - columns.get(selectedColumn).width--; - for (Cell cell: getSelectedColumn().cells) { - cell.setWidth(columns.get(selectedColumn).width); - cell.field.setWidth(columns.get(selectedColumn).width); - } - for (int i = selectedColumn + 1; i < columns.size(); i++) { - for (Cell cell: columns.get(i).cells) { - cell.setX(cell.getX() - 1); - } - } - alignGrid(false); - break; - case TMenu.MID_TABLE_COLUMN_WIDEN: - columns.get(selectedColumn).width++; - for (Cell cell: getSelectedColumn().cells) { - cell.setWidth(columns.get(selectedColumn).width); - cell.field.setWidth(columns.get(selectedColumn).width); - } - for (int i = selectedColumn + 1; i < columns.size(); i++) { - for (Cell cell: columns.get(i).cells) { - cell.setX(cell.getX() + 1); - } - } - alignGrid(false); - break; - case TMenu.MID_TABLE_FILE_SAVE_CSV: - // TODO - break; - case TMenu.MID_TABLE_FILE_SAVE_TEXT: - // TODO - break; - default: - super.onMenu(menu); - } - - alignGrid(false); + bottomRightCorner(); } // ------------------------------------------------------------------------ @@ -829,6 +863,129 @@ public class TTableWidget extends TWidget { } } + // Draw vertical borders. + if (leftBorder == Border.SINGLE) { + vLineXY((showRowLabels ? ROW_LABEL_WIDTH : 0), + (topBorder == Border.NONE ? 0 : 1) + + (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0), + getHeight(), '\u2502', borderColor); + } + for (int i = left; i < columns.size(); i++) { + if (columns.get(i).get(top).isVisible() == false) { + break; + } + if (columns.get(i).rightBorder == Border.SINGLE) { + vLineXY(columns.get(i).getX() + columns.get(i).width, + (topBorder == Border.NONE ? 0 : 1) + + (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0), + getHeight(), '\u2502', borderColor); + } + } + + // Draw horizontal borders. + if (topBorder == Border.SINGLE) { + hLineXY((showRowLabels ? ROW_LABEL_WIDTH : 0), + (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0), + getWidth(), '\u2500', borderColor); + } + for (int i = top; i < rows.size(); i++) { + if (rows.get(i).get(left).isVisible() == false) { + break; + } + if (rows.get(i).bottomBorder == Border.SINGLE) { + hLineXY((leftBorder == Border.NONE ? 0 : 1) + + (showRowLabels ? ROW_LABEL_WIDTH : 0), + rows.get(i).getY() + rows.get(i).height - 1, + getWidth(), '\u2500', borderColor); + } else if (rows.get(i).bottomBorder == Border.DOUBLE) { + hLineXY((leftBorder == Border.NONE ? 0 : 1) + + (showRowLabels ? ROW_LABEL_WIDTH : 0), + rows.get(i).getY() + rows.get(i).height - 1, + getWidth(), '\u2550', borderColor); + } else if (rows.get(i).bottomBorder == Border.THICK) { + hLineXY((leftBorder == Border.NONE ? 0 : 1) + + (showRowLabels ? ROW_LABEL_WIDTH : 0), + rows.get(i).getY() + rows.get(i).height - 1, + getWidth(), '\u2501', borderColor); + } + } + // Top-left corner if needed + if ((topBorder == Border.SINGLE) && (leftBorder == Border.SINGLE)) { + putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0), + (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0), + '\u250c', borderColor); + } + + // Now draw the correct corners + for (int i = top; i < rows.size(); i++) { + if (rows.get(i).get(left).isVisible() == false) { + break; + } + for (int j = left; j < columns.size(); j++) { + if (columns.get(j).get(i).isVisible() == false) { + break; + } + if ((i == top) && (topBorder == Border.SINGLE) + && (columns.get(j).rightBorder == Border.SINGLE) + ) { + // Top tee + putCharXY(columns.get(j).getX() + columns.get(j).width, + (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0), + '\u252c', borderColor); + } + if ((j == left) && (leftBorder == Border.SINGLE) + && (rows.get(i).bottomBorder == Border.SINGLE) + ) { + // Left tee + putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0), + rows.get(i).getY() + rows.get(i).height - 1, + '\u251c', borderColor); + } + if ((columns.get(j).rightBorder == Border.SINGLE) + && (rows.get(i).bottomBorder == Border.SINGLE) + ) { + // Intersection of single bars + putCharXY(columns.get(j).getX() + columns.get(j).width, + rows.get(i).getY() + rows.get(i).height - 1, + '\u253c', borderColor); + } + if ((j == left) && (leftBorder == Border.SINGLE) + && (rows.get(i).bottomBorder == Border.DOUBLE) + ) { + // Left tee: single bar vertical, double bar horizontal + putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0), + rows.get(i).getY() + rows.get(i).height - 1, + '\u255e', borderColor); + } + if ((j == left) && (leftBorder == Border.SINGLE) + && (rows.get(i).bottomBorder == Border.THICK) + ) { + // Left tee: single bar vertical, thick bar horizontal + putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0), + rows.get(i).getY() + rows.get(i).height - 1, + '\u251d', borderColor); + } + if ((columns.get(j).rightBorder == Border.SINGLE) + && (rows.get(i).bottomBorder == Border.DOUBLE) + ) { + // Intersection: single bar vertical, double bar + // horizontal + putCharXY(columns.get(j).getX() + columns.get(j).width, + rows.get(i).getY() + rows.get(i).height - 1, + '\u256a', borderColor); + } + if ((columns.get(j).rightBorder == Border.SINGLE) + && (rows.get(i).bottomBorder == Border.THICK) + ) { + // Intersection: single bar vertical, thick bar + // horizontal + putCharXY(columns.get(j).getX() + columns.get(j).width, + rows.get(i).getY() + rows.get(i).height - 1, + '\u253f', borderColor); + } + } + } + // Now draw the window borders. super.draw(); } @@ -837,6 +994,24 @@ public class TTableWidget extends TWidget { // TTable ----------------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Generate the default letter name for a column number. + * + * @param col column number to use for this column. Column 0 will be + * "A", column 1 will be "B", column 26 will be "AA", and so on. + */ + private String makeColumnLabel(int col) { + StringBuilder sb = new StringBuilder(); + for (;;) { + sb.append((char) ('A' + (col % 26))); + if (col < 26) { + break; + } + col /= 26; + } + return sb.reverse().toString(); + } + /** * Get the currently-selected cell. * @@ -898,7 +1073,7 @@ public class TTableWidget extends TWidget { } selectedColumn = column; activate(columns.get(selectedColumn).get(selectedRow)); - alignGrid(true); + alignGrid(); } /** @@ -922,7 +1097,79 @@ public class TTableWidget extends TWidget { } selectedRow = row; activate(columns.get(selectedColumn).get(selectedRow)); - alignGrid(true); + alignGrid(); + } + + /** + * Get the highlight row flag. + * + * @return true if the selected row is highlighted + */ + public boolean getHighlightRow() { + return highlightRow; + } + + /** + * Set the highlight row flag. + * + * @param highlightRow if true, the selected row will be highlighted + */ + public void setHighlightRow(final boolean highlightRow) { + this.highlightRow = highlightRow; + } + + /** + * Get the highlight column flag. + * + * @return true if the selected column is highlighted + */ + public boolean getHighlightColumn() { + return highlightColumn; + } + + /** + * Set the highlight column flag. + * + * @param highlightColumn if true, the selected column will be highlighted + */ + public void setHighlightColumn(final boolean highlightColumn) { + this.highlightColumn = highlightColumn; + } + + /** + * Get the show row labels flag. + * + * @return true if row labels are shown + */ + public boolean getShowRowLabels() { + return showRowLabels; + } + + /** + * Set the show row labels flag. + * + * @param showRowLabels if true, the row labels will be shown + */ + public void setShowRowLabels(final boolean showRowLabels) { + this.showRowLabels = showRowLabels; + } + + /** + * Get the show column labels flag. + * + * @return true if column labels are shown + */ + public boolean getShowColumnLabels() { + return showColumnLabels; + } + + /** + * Set the show column labels flag. + * + * @param showColumnLabels if true, the column labels will be shown + */ + public void setShowColumnLabels(final boolean showColumnLabels) { + this.showColumnLabels = showColumnLabels; } /** @@ -943,340 +1190,1170 @@ public class TTableWidget extends TWidget { return rows.size(); } + /** - * Get the full horizontal width of this table. - * - * @return the width required to render the entire table + * Push top and left to the bottom-most right corner of the available + * grid. */ - private int getMaximumWidth() { - int totalWidth = 0; + private void bottomRightCorner() { + int viewColumns = getWidth(); if (showRowLabels == true) { - // For now, all row labels are 8 cells wide. TODO: make this - // adjustable. - totalWidth += ROW_LABEL_WIDTH; + viewColumns -= ROW_LABEL_WIDTH; } - for (Cell cell: getSelectedRow().cells) { - totalWidth += cell.getWidth() + 1; - } - return totalWidth; + + // Set left and top such that the table stays on screen if possible. + top = rows.size() - getHeight(); + left = columns.size() - (getWidth() / (viewColumns / (COLUMN_DEFAULT_WIDTH + 1))); + // Now ensure the selection is visible. + alignGrid(); } /** - * Get the full vertical height of this table. - * - * @return the height required to render the entire table + * Align the grid so that the selected cell is fully visible. */ - private int getMaximumHeight() { - int totalHeight = 0; + private void alignGrid() { + + /* + System.err.println("alignGrid() # columns " + columns.size() + + " # rows " + rows.size()); + */ + + int viewColumns = getWidth(); + if (showRowLabels == true) { + viewColumns -= ROW_LABEL_WIDTH; + } + if (leftBorder != Border.NONE) { + viewColumns--; + } + int viewRows = getHeight(); if (showColumnLabels == true) { - // For now, all column labels are 1 cell tall. TODO: make this - // adjustable. - totalHeight += 1; + viewRows -= COLUMN_LABEL_HEIGHT; } - for (Cell cell: getSelectedColumn().cells) { - totalHeight += cell.getHeight(); - // TODO: handle top/bottom borders. + if (topBorder != Border.NONE) { + viewRows--; } - return totalHeight; - } - - /** - * 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 we pushed left or right, adjust the box to include the new + // selected cell. if (selectedColumn < left) { - left = selectedColumn; - resetColumnX = true; - resetRowY = true; + left = selectedColumn - 1; + } + if (left < 0) { + left = 0; } 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; + top = selectedRow - 1; } - - if ((resetColumnX == false) && (resetRowY == false)) { - // Nothing to do, bail out. - return; + if (top < 0) { + top = 0; } /* - * We start by assuming that all cells are visible, and then mark as - * invisible those that are outside the viewable area. + * viewColumns and viewRows now contain the available columns and + * rows available to view the selected cell. We adjust left and top + * to ensure the selected cell is within view, and then make all + * cells outside the box between (left, top) and (right, bottom) + * invisible. + * + * We need to calculate right and bottom now. */ - 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); + int right = left; + + boolean done = false; + while (!done) { + int rightCellX = (showRowLabels ? ROW_LABEL_WIDTH : 0); + if (leftBorder != Border.NONE) { + rightCellX++; + } + int maxCellX = rightCellX + viewColumns; + right = left; + boolean selectedIsVisible = false; + int selectedX = 0; + for (int x = left; x < columns.size(); x++) { + if (x == selectedColumn) { + selectedX = rightCellX; + if (selectedX + columns.get(x).width + 1 <= maxCellX) { + selectedIsVisible = true; + } } + rightCellX += columns.get(x).width + 1; + if (rightCellX >= maxCellX) { + break; + } + right++; + } + if (right < selectedColumn) { + // selectedColumn is outside the view range. Push left over, + // and calculate again. + left++; + } else if (left == selectedColumn) { + // selectedColumn doesn't fit inside the view range, but we + // can't go over any further either. Bail out. + done = true; + } else if (selectedIsVisible == false) { + // selectedColumn doesn't fit inside the view range, continue + // on. + left++; + } else { + // selectedColumn is fully visible, all done. + assert (selectedIsVisible == true); + done = true; } - } - // Adjust X locations to be visible ----------------------------------- + } // while (!done) - // 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; + // We have the left/right range correct, set cell visibility and + // column X positions. + int leftCellX = showRowLabels ? ROW_LABEL_WIDTH : 0; + if (leftBorder != Border.NONE) { + leftCellX++; + } + for (int x = 0; x < columns.size(); x++) { + if ((x < left) || (x > right)) { + for (int i = 0; i < rows.size(); i++) { + columns.get(x).get(i).setVisible(false); + columns.get(x).setX(getWidth() + 1); + } + continue; } - leftCellX += row.cells.get(i).getWidth() + 1; + for (int i = 0; i < rows.size(); i++) { + columns.get(x).get(i).setVisible(true); + } + columns.get(x).setX(leftCellX); + leftCellX += columns.get(x).width + 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 bottom = top; - int excessWidth = leftCellX + selectedColumnCell.getWidth() + 1 - getWidth(); - if (excessWidth > 0) { - leftCellX -= excessWidth; + done = false; + while (!done) { + int bottomCellY = (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0); + if (topBorder != Border.NONE) { + bottomCellY++; } - if (leftCellX < 0) { - if (showRowLabels == true) { - leftCellX = ROW_LABEL_WIDTH; - } else { - leftCellX = 0; + int maxCellY = bottomCellY + viewRows; + bottom = top; + for (int y = top; y < rows.size(); y++) { + bottomCellY += rows.get(y).height; + if (bottomCellY >= maxCellY) { + break; } + bottom++; } + if (bottom < selectedRow) { + // selectedRow is outside the view range. Push top down, and + // calculate again. + top++; + } else { + // selectedRow is inside the view range, done. + done = true; + } + } // while (!done) - /* - * 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); + // We have the top/bottom range correct, set cell visibility and + // row Y positions. + int topCellY = showColumnLabels ? COLUMN_LABEL_HEIGHT : 0; + if (topBorder != Border.NONE) { + topCellY++; + } + for (int y = 0; y < rows.size(); y++) { + if ((y < top) || (y > bottom)) { + for (int i = 0; i < columns.size(); i++) { + rows.get(y).get(i).setVisible(false); } + rows.get(y).setY(getHeight() + 1); + continue; + } + for (int i = 0; i < columns.size(); i++) { + rows.get(y).get(i).setVisible(true); + } + rows.get(y).setY(topCellY); + topCellY += rows.get(y).height; + } - 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; + // Last thing: cancel any edits that are not the selected cell. + for (int y = 0; y < rows.size(); y++) { + for (int x = 0; x < columns.size(); x++) { + if ((x == selectedColumn) && (y == selectedRow)) { + continue; } + rows.get(y).get(x).cancelEdit(); } + } + } - } // if (resetColumnX == true) + /** + * Load contents from file in CSV format. + * + * @param csvFile a File referencing the CSV data + * @throws IOException if a java.io operation throws + */ + public void loadCsvFile(final File csvFile) throws IOException { + BufferedReader reader = null; - // Adjust Y locations to be visible ----------------------------------- - // The same logic as above, but applied to the column Y. + try { + reader = new BufferedReader(new FileReader(csvFile)); - // 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); + String line = null; + boolean first = true; + for (line = reader.readLine(); line != null; + line = reader.readLine()) { - if (resetRowY == true) { + List list = StringUtils.fromCsv(line); + if (list.size() == 0) { + continue; + } - // We need to adjust everything so that the selected cell is - // visible. + if (list.size() > columns.size()) { + int n = list.size() - columns.size(); + for (int i = 0; i < n; i++) { + selectedColumn = columns.size() - 1; + insertColumnRight(selectedColumn); + } + } + assert (list.size() == columns.size()); - int excessHeight = topCellY + selectedRowCell.getHeight() - getHeight() - 1; - if (showColumnLabels == true) { - excessHeight += COLUMN_LABEL_HEIGHT; + if (first) { + // First row: just replace what is here. + selectedRow = 0; + first = false; + } else { + // All other rows: append to the end. + selectedRow = rows.size() - 1; + insertRowBelow(selectedRow); + selectedRow = rows.size() - 1; + } + for (int i = 0; i < list.size(); i++) { + rows.get(selectedRow).get(i).setText(list.get(i)); + } } - if (excessHeight > 0) { - topCellY -= excessHeight; + } finally { + if (reader != null) { + reader.close(); } - if (topCellY < 0) { - if (showColumnLabels == true) { - topCellY = COLUMN_LABEL_HEIGHT; - } else { - topCellY = 0; + } + + left = 0; + top = 0; + selectedRow = 0; + selectedColumn = 0; + alignGrid(); + activate(columns.get(selectedColumn).get(selectedRow)); + } + + /** + * Save contents to file in CSV format. + * + * @param filename file to save to + * @throws IOException if a java.io operation throws + */ + public void saveToCsvFilename(final String filename) throws IOException { + BufferedWriter writer = null; + + try { + writer = new BufferedWriter(new FileWriter(filename)); + for (Row row: rows) { + List list = new ArrayList(row.cells.size()); + for (Cell cell: row.cells) { + list.add(cell.getText()); } + writer.write(StringUtils.toCsv(list)); + writer.write("\n"); + } + } finally { + if (writer != null) { + writer.close(); } + } + } - /* - * 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); + /** + * Save contents to file in text format with lines. + * + * @param filename file to save to + * @throws IOException if a java.io operation throws + */ + public void saveToTextFilename(final String filename) throws IOException { + BufferedWriter writer = null; + + try { + writer = new BufferedWriter(new FileWriter(filename)); + + if ((topBorder == Border.SINGLE) && (leftBorder == Border.SINGLE)) { + // Emit top-left corner. + writer.write("\u250c"); + } + + if (topBorder == Border.SINGLE) { + int cellI = 0; + for (Cell cell: rows.get(0).cells) { + for (int i = 0; i < columns.get(cellI).width; i++) { + writer.write("\u2500"); + } + + if (columns.get(cellI).rightBorder == Border.SINGLE) { + if (cellI < columns.size() - 1) { + // Emit top tee. + writer.write("\u252c"); } else { - columns.get(x).get(y).setVisible(true); + // Emit top-right corner. + writer.write("\u2510"); } - top--; - } else { - // This cell won't be visible. - columns.get(x).get(y).setVisible(false); } + cellI++; } - top++; + } + writer.write("\n"); - // 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()) + int rowI = 0; + for (Row row: rows) { + + if (leftBorder == Border.SINGLE) { + // Emit left border. + writer.write("\u2502"); + } + + int cellI = 0; + for (Cell cell: row.cells) { + writer.write(String.format("%" + + columns.get(cellI).width + "s", cell.getText())); + + if (columns.get(cellI).rightBorder == Border.SINGLE) { + // Emit right border. + writer.write("\u2502"); + } + cellI++; + } + writer.write("\n"); + + if (row.bottomBorder == Border.NONE) { + // All done, move on to the next row. + continue; + } + + // Emit the bottom borders and intersections. + if ((leftBorder == Border.SINGLE) + && (row.bottomBorder != Border.NONE) ) { - // This column isn't visible. - columns.get(x).get(selectedRow).setVisible(false); - } else { - columns.get(x).get(selectedRow).setVisible(true); + if (rowI < rows.size() - 1) { + if (row.bottomBorder == Border.SINGLE) { + // Emit left tee. + writer.write("\u251c"); + } else if (row.bottomBorder == Border.DOUBLE) { + // Emit left tee (double). + writer.write("\u255e"); + } else if (row.bottomBorder == Border.THICK) { + // Emit left tee (thick). + writer.write("\u251d"); + } + } + + if (rowI == rows.size() - 1) { + if (row.bottomBorder == Border.SINGLE) { + // Emit left bottom corner. + writer.write("\u2514"); + } else if (row.bottomBorder == Border.DOUBLE) { + // Emit left bottom corner (double). + writer.write("\u2558"); + } else if (row.bottomBorder == Border.THICK) { + // Emit left bottom corner (thick). + writer.write("\u2515"); + } + } } - 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); + cellI = 0; + for (Cell cell: row.cells) { + + for (int i = 0; i < columns.get(cellI).width; i++) { + if (row.bottomBorder == Border.SINGLE) { + writer.write("\u2500"); + } + if (row.bottomBorder == Border.DOUBLE) { + writer.write("\u2550"); } - } else { - // This cell won't be visible. - columns.get(x).get(y).setVisible(false); + if (row.bottomBorder == Border.THICK) { + writer.write("\u2501"); + } + } + + if ((rowI < rows.size() - 1) + && (cellI == columns.size() - 1) + && (row.bottomBorder == Border.SINGLE) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit right tee. + writer.write("\u2524"); + } + if ((rowI < rows.size() - 1) + && (cellI == columns.size() - 1) + && (row.bottomBorder == Border.DOUBLE) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit right tee (double). + writer.write("\u2561"); + } + if ((rowI < rows.size() - 1) + && (cellI == columns.size() - 1) + && (row.bottomBorder == Border.THICK) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit right tee (thick). + writer.write("\u2525"); + } + if ((rowI == rows.size() - 1) + && (cellI == columns.size() - 1) + && (row.bottomBorder == Border.SINGLE) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit right bottom corner. + writer.write("\u2518"); + } + if ((rowI == rows.size() - 1) + && (cellI == columns.size() - 1) + && (row.bottomBorder == Border.DOUBLE) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit right bottom corner (double). + writer.write("\u255b"); + } + if ((rowI == rows.size() - 1) + && (cellI == columns.size() - 1) + && (row.bottomBorder == Border.THICK) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit right bottom corner (thick). + writer.write("\u2519"); + } + if ((rowI < rows.size() - 1) + && (cellI < columns.size() - 1) + && (row.bottomBorder == Border.SINGLE) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit intersection. + writer.write("\u253c"); + } + if ((rowI < rows.size() - 1) + && (cellI < columns.size() - 1) + && (row.bottomBorder == Border.DOUBLE) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit intersection (double). + writer.write("\u256a"); + } + if ((rowI < rows.size() - 1) + && (cellI < columns.size() - 1) + && (row.bottomBorder == Border.THICK) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit intersection (thick). + writer.write("\u253f"); + } + if ((rowI == rows.size() - 1) + && (cellI < columns.size() - 1) + && (row.bottomBorder == Border.SINGLE) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit bottom tee. + writer.write("\u2534"); + } + if ((rowI == rows.size() - 1) + && (cellI < columns.size() - 1) + && (row.bottomBorder == Border.DOUBLE) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit bottom tee (double). + writer.write("\u2567"); + } + if ((rowI == rows.size() - 1) + && (cellI < columns.size() - 1) + && (row.bottomBorder == Border.THICK) + && (columns.get(cellI).rightBorder == Border.SINGLE) + ) { + // Emit bottom tee (thick). + writer.write("\u2537"); } - newCellY += columns.get(x).get(y).getHeight(); + + cellI++; } + + writer.write("\n"); + rowI++; } + } finally { + if (writer != null) { + writer.close(); + } + } + } - } // if (resetRowY == true) + /** + * Set the selected cell location. + * + * @param column the selected cell location column + * @param row the selected cell location row + */ + public void setSelectedCell(final int column, final int row) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + selectedColumn = column; + selectedRow = row; + alignGrid(); + } + /** + * Get a particular cell. + * + * @param column the cell column + * @param row the cell row + * @return the cell + */ + public Cell getCell(final int column, final int row) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + return rows.get(row).get(column); } /** - * Save contents to file. + * Get the text of a particular cell. * - * @param filename file to save to - * @throws IOException if a java.io operation throws + * @param column the cell column + * @param row the cell row + * @return the text in the cell + */ + public String getCellText(final int column, final int row) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + return rows.get(row).get(column).getText(); + } + + /** + * Set the text of a particular cell. + * + * @param column the cell column + * @param row the cell row + * @param text the text to put into the cell + */ + public void setCellText(final int column, final int row, + final String text) { + + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + rows.get(row).get(column).setText(text); + } + + /** + * Set the action to perform when the user presses enter on a particular + * cell. + * + * @param column the cell column + * @param row the cell row + * @param action the action to perform when the user presses enter on the + * cell + */ + public void setCellEnterAction(final int column, final int row, + final TAction action) { + + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + rows.get(row).get(column).field.setEnterAction(action); + } + + /** + * Set the action to perform when the user updates a particular cell. + * + * @param column the cell column + * @param row the cell row + * @param action the action to perform when the user updates the cell + */ + public void setCellUpdateAction(final int column, final int row, + final TAction action) { + + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + rows.get(row).get(column).field.setUpdateAction(action); + } + + /** + * Get the width of a column. + * + * @param column the column number + * @return the width of the column + */ + public int getColumnWidth(final int column) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + return columns.get(column).width; + } + + /** + * Set the width of a column. + * + * @param column the column number + * @param width the new width of the column + */ + public void setColumnWidth(final int column, final int width) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + + if (width < 4) { + // Columns may not be smaller than 4 cells wide. + return; + } + + int delta = width - columns.get(column).width; + columns.get(column).width = width; + for (Cell cell: columns.get(column).cells) { + cell.setWidth(columns.get(column).width); + cell.field.setWidth(columns.get(column).width); + } + for (int i = column + 1; i < columns.size(); i++) { + columns.get(i).setX(columns.get(i).getX() + delta); + } + if (column == columns.size() - 1) { + bottomRightCorner(); + } else { + alignGrid(); + } + } + + /** + * Get the label of a column. + * + * @param column the column number + * @return the label of the column + */ + public String getColumnLabel(final int column) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + return columns.get(column).label; + } + + /** + * Set the label of a column. + * + * @param column the column number + * @param label the new label of the column + */ + public void setColumnLabel(final int column, final String label) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + columns.get(column).label = label; + } + + /** + * Get the label of a row. + * + * @param row the row number + * @return the label of the row + */ + public String getRowLabel(final int row) { + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + return rows.get(row).label; + } + + /** + * Set the label of a row. + * + * @param row the row number + * @param label the new label of the row + */ + public void setRowLabel(final int row, final String label) { + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + rows.get(row).label = label; + } + + /** + * Insert one row at a particular index. + * + * @param idx the row number + */ + private void insertRowAt(final int idx) { + Row newRow = new Row(idx); + for (int i = 0; i < columns.size(); i++) { + Cell cell = new Cell(this, columns.get(i).getX(), + rows.get(idx).getY(), COLUMN_DEFAULT_WIDTH, 1, i, idx); + newRow.add(cell); + columns.get(i).cells.add(idx, cell); + } + rows.add(idx, newRow); + + for (int x = 0; x < columns.size(); x++) { + for (int y = idx; y < rows.size(); y++) { + columns.get(x).get(y).row = y; + columns.get(x).get(y).column = x; + } + } + for (int i = idx + 1; i < rows.size(); i++) { + String oldRowLabel = Integer.toString(i - 1); + if (rows.get(i).label.equals(oldRowLabel)) { + rows.get(i).label = Integer.toString(i); + } + } + alignGrid(); + } + + /** + * Insert one row above a particular row. + * + * @param row the row number + */ + public void insertRowAbove(final int row) { + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + insertRowAt(row); + selectedRow++; + activate(columns.get(selectedColumn).get(selectedRow)); + } + + /** + * Insert one row below a particular row. + * + * @param row the row number + */ + public void insertRowBelow(final int row) { + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + int idx = row + 1; + if (idx < rows.size()) { + insertRowAt(idx); + activate(columns.get(selectedColumn).get(selectedRow)); + return; + } + + // row is the last row, we need to perform an append. + Row newRow = new Row(idx); + for (int i = 0; i < columns.size(); i++) { + Cell cell = new Cell(this, columns.get(i).getX(), + rows.get(row).getY(), COLUMN_DEFAULT_WIDTH, 1, i, idx); + newRow.add(cell); + columns.get(i).cells.add(cell); + } + rows.add(newRow); + alignGrid(); + activate(columns.get(selectedColumn).get(selectedRow)); + } + + /** + * Delete a particular row. + * + * @param row the row number + */ + public void deleteRow(final int row) { + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + if (rows.size() == 1) { + // Don't delete the last row. + return; + } + for (int i = 0; i < columns.size(); i++) { + Cell cell = columns.get(i).cells.remove(row); + getChildren().remove(cell); + } + rows.remove(row); + + for (int x = 0; x < columns.size(); x++) { + for (int y = row; y < rows.size(); y++) { + columns.get(x).get(y).row = y; + columns.get(x).get(y).column = x; + } + } + for (int i = row; i < rows.size(); i++) { + String oldRowLabel = Integer.toString(i + 1); + if (rows.get(i).label.equals(oldRowLabel)) { + rows.get(i).label = Integer.toString(i); + } + } + if (selectedRow == rows.size()) { + selectedRow--; + } + activate(columns.get(selectedColumn).get(selectedRow)); + bottomRightCorner(); + } + + /** + * Insert one column at a particular index. + * + * @param idx the column number + */ + private void insertColumnAt(final int idx) { + Column newColumn = new Column(idx); + for (int i = 0; i < rows.size(); i++) { + Cell cell = new Cell(this, columns.get(idx).getX(), + rows.get(i).getY(), COLUMN_DEFAULT_WIDTH, 1, idx, i); + newColumn.add(cell); + rows.get(i).cells.add(idx, cell); + } + columns.add(idx, newColumn); + + for (int x = idx; x < columns.size(); x++) { + for (int y = 0; y < rows.size(); y++) { + columns.get(x).get(y).row = y; + columns.get(x).get(y).column = x; + } + } + for (int i = idx + 1; i < columns.size(); i++) { + String oldColumnLabel = makeColumnLabel(i - 1); + if (columns.get(i).label.equals(oldColumnLabel)) { + columns.get(i).label = makeColumnLabel(i); + } + } + alignGrid(); + } + + /** + * Insert one column to the left of a particular column. + * + * @param column the column number + */ + public void insertColumnLeft(final int column) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + insertColumnAt(column); + selectedColumn++; + activate(columns.get(selectedColumn).get(selectedRow)); + } + + /** + * Insert one column to the right of a particular column. + * + * @param column the column number + */ + public void insertColumnRight(final int column) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + int idx = column + 1; + if (idx < columns.size()) { + insertColumnAt(idx); + activate(columns.get(selectedColumn).get(selectedRow)); + return; + } + + // column is the last column, we need to perform an append. + Column newColumn = new Column(idx); + for (int i = 0; i < rows.size(); i++) { + Cell cell = new Cell(this, columns.get(column).getX(), + rows.get(i).getY(), COLUMN_DEFAULT_WIDTH, 1, idx, i); + newColumn.add(cell); + rows.get(i).cells.add(cell); + } + columns.add(newColumn); + alignGrid(); + activate(columns.get(selectedColumn).get(selectedRow)); + } + + /** + * Delete a particular column. + * + * @param column the column number + */ + public void deleteColumn(final int column) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + if (columns.size() == 1) { + // Don't delete the last column. + return; + } + for (int i = 0; i < rows.size(); i++) { + Cell cell = rows.get(i).cells.remove(column); + getChildren().remove(cell); + } + columns.remove(column); + + for (int x = column; x < columns.size(); x++) { + for (int y = 0; y < rows.size(); y++) { + columns.get(x).get(y).row = y; + columns.get(x).get(y).column = x; + } + } + for (int i = column; i < columns.size(); i++) { + String oldColumnLabel = makeColumnLabel(i + 1); + if (columns.get(i).label.equals(oldColumnLabel)) { + columns.get(i).label = makeColumnLabel(i); + } + } + if (selectedColumn == columns.size()) { + selectedColumn--; + } + activate(columns.get(selectedColumn).get(selectedRow)); + bottomRightCorner(); + } + + /** + * Delete the selected cell, shifting cells over to the left. + */ + public void deleteCellShiftLeft() { + // All we do is copy the text from every cell in this row over. + for (int i = selectedColumn + 1; i < columns.size(); i++) { + setCellText(i - 1, selectedRow, getCellText(i, selectedRow)); + } + setCellText(columns.size() - 1, selectedRow, ""); + } + + /** + * Delete the selected cell, shifting cells from below up. + */ + public void deleteCellShiftUp() { + // All we do is copy the text from every cell in this column up. + for (int i = selectedRow + 1; i < rows.size(); i++) { + setCellText(selectedColumn, i - 1, getCellText(selectedColumn, i)); + } + setCellText(selectedColumn, rows.size() - 1, ""); + } + + /** + * Set a particular cell read-only (non-editable) or not. + * + * @param column the cell column + * @param row the cell row + * @param readOnly if true, the cell will be non-editable + */ + public void setCellReadOnly(final int column, final int row, + final boolean readOnly) { + + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + rows.get(row).get(column).setReadOnly(readOnly); + } + + /** + * Set an entire row of cells read-only (non-editable) or not. + * + * @param row the row number + * @param readOnly if true, the cells will be non-editable + */ + public void setRowReadOnly(final int row, final boolean readOnly) { + if ((row < 0) || (row > rows.size() - 1)) { + throw new IndexOutOfBoundsException("Row count is " + + rows.size() + ", requested index " + row); + } + for (Cell cell: rows.get(row).cells) { + cell.setReadOnly(readOnly); + } + } + + /** + * Set an entire column of cells read-only (non-editable) or not. + * + * @param column the column number + * @param readOnly if true, the cells will be non-editable + */ + public void setColumnReadOnly(final int column, final boolean readOnly) { + if ((column < 0) || (column > columns.size() - 1)) { + throw new IndexOutOfBoundsException("Column count is " + + columns.size() + ", requested index " + column); + } + for (Cell cell: columns.get(column).cells) { + cell.setReadOnly(readOnly); + } + } + + /** + * Set all borders across the entire table to Border.NONE. + */ + public void setBorderAllNone() { + topBorder = Border.NONE; + leftBorder = Border.NONE; + for (int i = 0; i < columns.size(); i++) { + columns.get(i).rightBorder = Border.NONE; + } + for (int i = 0; i < rows.size(); i++) { + rows.get(i).bottomBorder = Border.NONE; + rows.get(i).height = 1; + } + bottomRightCorner(); + } + + /** + * Set all borders across the entire table to Border.SINGLE. + */ + public void setBorderAllSingle() { + topBorder = Border.SINGLE; + leftBorder = Border.SINGLE; + for (int i = 0; i < columns.size(); i++) { + columns.get(i).rightBorder = Border.SINGLE; + } + for (int i = 0; i < rows.size(); i++) { + rows.get(i).bottomBorder = Border.SINGLE; + rows.get(i).height = 2; + } + alignGrid(); + } + + /** + * Set all borders around the selected cell to Border.NONE. + */ + public void setBorderCellNone() { + if (selectedRow == 0) { + topBorder = Border.NONE; + } + if (selectedColumn == 0) { + leftBorder = Border.NONE; + } + if (selectedColumn > 0) { + columns.get(selectedColumn - 1).rightBorder = Border.NONE; + } + columns.get(selectedColumn).rightBorder = Border.NONE; + if (selectedRow > 0) { + rows.get(selectedRow - 1).bottomBorder = Border.NONE; + rows.get(selectedRow - 1).height = 1; + } + rows.get(selectedRow).bottomBorder = Border.NONE; + rows.get(selectedRow).height = 1; + bottomRightCorner(); + } + + /** + * Set all borders around the selected cell to Border.SINGLE. + */ + public void setBorderCellSingle() { + if (selectedRow == 0) { + topBorder = Border.SINGLE; + } + if (selectedColumn == 0) { + leftBorder = Border.SINGLE; + } + if (selectedColumn > 0) { + columns.get(selectedColumn - 1).rightBorder = Border.SINGLE; + } + columns.get(selectedColumn).rightBorder = Border.SINGLE; + if (selectedRow > 0) { + rows.get(selectedRow - 1).bottomBorder = Border.SINGLE; + rows.get(selectedRow - 1).height = 2; + } + rows.get(selectedRow).bottomBorder = Border.SINGLE; + rows.get(selectedRow).height = 2; + alignGrid(); + } + + /** + * Set the column border to the right of the selected cell to + * Border.SINGLE. + */ + public void setBorderColumnRightSingle() { + columns.get(selectedColumn).rightBorder = Border.SINGLE; + alignGrid(); + } + + /** + * Set the column border to the right of the selected cell to + * Border.SINGLE. + */ + public void setBorderColumnLeftSingle() { + if (selectedColumn == 0) { + leftBorder = Border.SINGLE; + } else { + columns.get(selectedColumn - 1).rightBorder = Border.SINGLE; + } + alignGrid(); + } + + /** + * Set the row border above the selected cell to Border.SINGLE. + */ + public void setBorderRowAboveSingle() { + if (selectedRow == 0) { + topBorder = Border.SINGLE; + } else { + rows.get(selectedRow - 1).bottomBorder = Border.SINGLE; + rows.get(selectedRow - 1).height = 2; + } + alignGrid(); + } + + /** + * Set the row border below the selected cell to Border.SINGLE. + */ + public void setBorderRowBelowSingle() { + rows.get(selectedRow).bottomBorder = Border.SINGLE; + rows.get(selectedRow).height = 2; + alignGrid(); + } + + /** + * Set the row border below the selected cell to Border.DOUBLE. + */ + public void setBorderRowBelowDouble() { + rows.get(selectedRow).bottomBorder = Border.DOUBLE; + rows.get(selectedRow).height = 2; + alignGrid(); + } + + /** + * Set the row border below the selected cell to Border.THICK. */ - public void saveToFilename(final String filename) throws IOException { - // TODO + public void setBorderRowBelowThick() { + rows.get(selectedRow).bottomBorder = Border.THICK; + rows.get(selectedRow).height = 2; + alignGrid(); } }