/* * Jexer - Java Text User Interface * * The MIT License (MIT) * * Copyright (C) 2019 Kevin Lamonte * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * @author Kevin Lamonte [kevin.lamonte@gmail.com] * @version 1 */ 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.TMouseEvent; import jexer.event.TResizeEvent; import static jexer.TKeypress.*; /** * TTableWidget is used to display and edit regular two-dimensional tables of * cells. * * This class was inspired by a TTable implementation originally developed by * David "Niki" ROULET [niki@nikiroo.be], made available under MIT at * https://github.com/nikiroo/jexer/tree/ttable_pull. */ public class TTableWidget extends TWidget { // ------------------------------------------------------------------------ // Constants -------------------------------------------------------------- // ------------------------------------------------------------------------ /** * Available borders for cells. */ public enum Border { /** * No border. */ NONE, /** * Single bar: \u2502 (vertical) and \u2500 (horizontal). */ SINGLE, /** * Double bar: \u2551 (vertical) and \u2550 (horizontal). */ DOUBLE, /** * 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. */ private static final int ROW_LABEL_WIDTH = 8; /** * Column label height. */ 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 = (DEBUG ? 10 : 0); /** * Extra columns to add. */ private static final int EXTRA_COLUMNS = (DEBUG ? 3 : 0); // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- // ------------------------------------------------------------------------ /** * The underlying data, organized as columns. */ private ArrayList columns = new ArrayList(); /** * The underlying data, organized as rows. */ private ArrayList rows = new ArrayList(); /** * The row in model corresponding to the top-left visible cell. */ private int top = 0; /** * The column in model corresponding to the top-left visible cell. */ private int left = 0; /** * The row in model corresponding to the currently selected cell. */ private int selectedRow = 0; /** * The column in model corresponding to the currently selected cell. */ private int selectedColumn = 0; /** * If true, highlight the entire row of the currently-selected cell. */ private boolean highlightRow = false; /** * If true, highlight the entire column of the currently-selected cell. */ private boolean highlightColumn = false; /** * If true, show the row labels as the first column. */ private boolean showRowLabels = true; /** * If true, show the column labels as the first row. */ private boolean showColumnLabels = true; /** * The top border for the first row. */ private Border topBorder = Border.NONE; /** * The left border for the first column. */ private Border leftBorder = Border.NONE; /** * Column represents a column of cells. */ public class Column { /** * X position of this column. */ private int x = 0; /** * Width of column. */ private int width = COLUMN_DEFAULT_WIDTH; /** * The cells of this column. */ private ArrayList cells = new ArrayList(); /** * Column label. */ private String label = ""; /** * The right border for this column. */ private Border rightBorder = Border.NONE; /** * Constructor sets label to lettered column. * * @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. */ Column(int col) { label = makeColumnLabel(col); } /** * Add an entry to this column. * * @param cell the cell to add */ public void add(final Cell cell) { cells.add(cell); } /** * Get an entry from this column. * * @param row the entry index to get * @return the cell at row */ 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; } } /** * Row represents a row of cells. */ public class Row { /** * Y position of this row. */ private int y = 0; /** * Height of row. */ private int height = 1; /** * The cells of this row. */ private ArrayList cells = new ArrayList(); /** * Row label. */ private String label = ""; /** * The bottom border for this row. */ private Border bottomBorder = Border.NONE; /** * Constructor sets label to numbered row. * * @param row row number to use for this row */ Row(final int row) { label = Integer.toString(row); } /** * Add an entry to this column. * * @param cell the cell to add */ public void add(final Cell cell) { cells.add(cell); } /** * Get an entry from this row. * * @param column the entry index to get * @return the cell at column */ 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; } } /** * Cell represents an editable cell in the table. Normally, navigation * to a cell only highlights it; pressing Enter or F2 will switch to * editing mode. */ public class Cell extends TWidget { // -------------------------------------------------------------------- // Variables ---------------------------------------------------------- // -------------------------------------------------------------------- /** * The field containing the cell's data. */ private TField field; /** * The column of this cell. */ private int column; /** * The row of this cell. */ private int row; /** * If true, the cell is being edited. */ private boolean isEditing = false; /** * If true, the cell is read-only (non-editable). */ private boolean readOnly = false; /** * Text of field before editing. */ private String fieldText; // -------------------------------------------------------------------- // Constructors ------------------------------------------------------- // -------------------------------------------------------------------- /** * 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 * @param column column index of this cell * @param row row index of this cell */ public Cell(final TTableWidget parent, final int x, final int y, final int width, final int height, final int column, final int row) { super(parent, x, y, width, height); this.column = column; this.row = row; field = addField(0, 0, width, false); field.setEnabled(false); field.setBackgroundChar(' '); } // -------------------------------------------------------------------- // Event handlers ----------------------------------------------------- // -------------------------------------------------------------------- /** * Handle mouse double-click events. * * @param mouse mouse double-click event */ @Override public void onMouseDoubleClick(final TMouseEvent mouse) { // Use TWidget's code to pass the event to the children. super.onMouseDown(mouse); // Double-click means to start editing. fieldText = field.getText(); isEditing = true; field.setEnabled(true); activate(field); if (isActive()) { // Let the table know that I was activated. ((TTableWidget) getParent()).selectedRow = row; ((TTableWidget) getParent()).selectedColumn = column; ((TTableWidget) getParent()).alignGrid(); } } /** * Handle mouse press events. * * @param mouse mouse button press event */ @Override public void onMouseDown(final TMouseEvent mouse) { // Use TWidget's code to pass the event to the children. super.onMouseDown(mouse); if (isActive()) { // Let the table know that I was activated. ((TTableWidget) getParent()).selectedRow = row; ((TTableWidget) getParent()).selectedColumn = column; ((TTableWidget) getParent()).alignGrid(); } } /** * Handle mouse release events. * * @param mouse mouse button release event */ @Override public void onMouseUp(final TMouseEvent mouse) { // Use TWidget's code to pass the event to the children. super.onMouseDown(mouse); if (isActive()) { // Let the table know that I was activated. ((TTableWidget) getParent()).selectedRow = row; ((TTableWidget) getParent()).selectedColumn = column; ((TTableWidget) getParent()).alignGrid(); } } /** * Handle keystrokes. * * @param keypress keystroke event */ @Override 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. 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); return; } // Pass down to field. super.onKeypress(keypress); } if (keypress.equals(kbEnter) || keypress.equals(kbF2)) { // Enter or F2 starts editing. fieldText = field.getText(); isEditing = true; field.setEnabled(true); activate(field); return; } } // -------------------------------------------------------------------- // TWidget ------------------------------------------------------------ // -------------------------------------------------------------------- /** * Draw this cell. */ @Override public void draw() { TTableWidget table = (TTableWidget) getParent(); if (isAbsoluteActive()) { if (isEditing) { field.setActiveColorKey("tfield.active"); field.setInactiveColorKey("tfield.inactive"); } else { field.setActiveColorKey("ttable.selected"); field.setInactiveColorKey("ttable.selected"); } } else if (((table.selectedColumn == column) && ((table.selectedRow == row) || (table.highlightColumn == true))) || ((table.selectedRow == row) && ((table.selectedColumn == column) || (table.highlightRow == true))) ) { field.setActiveColorKey("ttable.active"); field.setInactiveColorKey("ttable.active"); } else { field.setActiveColorKey("ttable.active"); field.setInactiveColorKey("ttable.inactive"); } assert (isVisible() == true); super.draw(); } // -------------------------------------------------------------------- // TTable.Cell -------------------------------------------------------- // -------------------------------------------------------------------- /** * Get field text. * * @return field text */ public final String getText() { return field.getText(); } /** * Set field text. * * @param text the new field text */ public void setText(final String text) { 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; } } // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ /** * 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 * @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 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. 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 (columns.size() < gridColumns) { columns.add(new Column(column + 1)); } } if (row < gridRows - 1) { rows.add(new Row(row + 1)); } } 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(); } /** * 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); } // ------------------------------------------------------------------------ // Event handlers --------------------------------------------------------- // ------------------------------------------------------------------------ /** * Handle mouse press events. * * @param mouse mouse button press event */ @Override public void onMouseDown(final TMouseEvent mouse) { if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) { // Treat wheel up/down as 3 up/down TKeypressEvent keyEvent; if (mouse.isMouseWheelUp()) { keyEvent = new TKeypressEvent(kbUp); } else { keyEvent = new TKeypressEvent(kbDown); } for (int i = 0; i < 3; i++) { onKeypress(keyEvent); } return; } // Use TWidget's code to pass the event to the children. super.onMouseDown(mouse); } /** * Handle keystrokes. * * @param keypress keystroke event */ @Override public void onKeypress(final TKeypressEvent keypress) { if (keypress.equals(kbTab) || keypress.equals(kbShiftTab) ) { // Squash tab and back-tab. They don't make sense in the TTable // grid context. return; } // If editing, pass to that cell and do nothing else. if (getSelectedCell().isEditing) { super.onKeypress(keypress); return; } if (keypress.equals(kbLeft)) { // Left if (selectedColumn > 0) { selectedColumn--; } activate(columns.get(selectedColumn).get(selectedRow)); } else if (keypress.equals(kbRight)) { // Right if (selectedColumn < columns.size() - 1) { selectedColumn++; } activate(columns.get(selectedColumn).get(selectedRow)); } else if (keypress.equals(kbUp)) { // Up if (selectedRow > 0) { selectedRow--; } activate(columns.get(selectedColumn).get(selectedRow)); } else if (keypress.equals(kbDown)) { // Down if (selectedRow < rows.size() - 1) { selectedRow++; } activate(columns.get(selectedColumn).get(selectedRow)); } else if (keypress.equals(kbHome)) { // Home - leftmost column selectedColumn = 0; activate(columns.get(selectedColumn).get(selectedRow)); } else if (keypress.equals(kbEnd)) { // End - rightmost column selectedColumn = columns.size() - 1; activate(columns.get(selectedColumn).get(selectedRow)); } else if (keypress.equals(kbPgUp)) { // PgUp - Treat like multiple up for (int i = 0; i < getHeight() - 2; i++) { if (selectedRow > 0) { selectedRow--; } } activate(columns.get(selectedColumn).get(selectedRow)); } else if (keypress.equals(kbPgDn)) { // PgDn - Treat like multiple up for (int i = 0; i < getHeight() - 2; i++) { if (selectedRow < rows.size() - 1) { selectedRow++; } } activate(columns.get(selectedColumn).get(selectedRow)); } 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)); } 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)); } else { // Pass to the Cell. super.onKeypress(keypress); } // We may have scrolled off screen. Reset positions as needed to // make the newly selected cell visible. alignGrid(); } /** * Handle widget resize events. * * @param event resize event */ @Override public void onResize(final TResizeEvent event) { super.onResize(event); bottomRightCorner(); } // ------------------------------------------------------------------------ // TWidget ---------------------------------------------------------------- // ------------------------------------------------------------------------ /** * Draw the table row/column labels, and borders. */ @Override public void draw() { CellAttributes labelColor = getTheme().getColor("ttable.label"); CellAttributes labelColorSelected = getTheme().getColor("ttable.label.selected"); CellAttributes borderColor = getTheme().getColor("ttable.border"); // Column labels. if (showColumnLabels == true) { for (int i = left; i < columns.size(); i++) { if (columns.get(i).get(top).isVisible() == false) { break; } putStringXY(columns.get(i).get(top).getX(), 0, String.format(" %-" + (columns.get(i).width - 2) + "s ", columns.get(i).label), (i == selectedColumn ? labelColorSelected : labelColor)); } } // Row labels. if (showRowLabels == true) { for (int i = top; i < rows.size(); i++) { if (rows.get(i).get(left).isVisible() == false) { break; } putStringXY(0, rows.get(i).get(left).getY(), String.format(" %-6s ", rows.get(i).label), (i == selectedRow ? labelColorSelected : labelColor)); } } // 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(); } // ------------------------------------------------------------------------ // 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. * * @return the selected cell */ public Cell getSelectedCell() { assert (rows.get(selectedRow) != null); assert (rows.get(selectedRow).get(selectedColumn) != null); assert (columns.get(selectedColumn) != null); assert (columns.get(selectedColumn).get(selectedRow) != null); assert (rows.get(selectedRow).get(selectedColumn) == columns.get(selectedColumn).get(selectedRow)); return (columns.get(selectedColumn).get(selectedRow)); } /** * Get the currently-selected column. * * @return the selected column */ public Column getSelectedColumn() { assert (selectedColumn >= 0); assert (columns.size() > selectedColumn); assert (columns.get(selectedColumn) != null); return columns.get(selectedColumn); } /** * Get the currently-selected row. * * @return the selected row */ public Row getSelectedRow() { assert (selectedRow >= 0); assert (rows.size() > selectedRow); assert (rows.get(selectedRow) != null); return rows.get(selectedRow); } /** * 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(); } /** * 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(); } /** * 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; } /** * 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(); } /** * Push top and left to the bottom-most right corner of the available * grid. */ private void bottomRightCorner() { int viewColumns = getWidth(); if (showRowLabels == true) { viewColumns -= ROW_LABEL_WIDTH; } // 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(); } /** * Align the grid so that the selected cell is fully visible. */ 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) { viewRows -= COLUMN_LABEL_HEIGHT; } if (topBorder != Border.NONE) { viewRows--; } // If we pushed left or right, adjust the box to include the new // selected cell. if (selectedColumn < left) { left = selectedColumn - 1; } if (left < 0) { left = 0; } if (selectedRow < top) { top = selectedRow - 1; } if (top < 0) { top = 0; } /* * 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. */ 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; } } // while (!done) // 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; } 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; } int bottom = top; done = false; while (!done) { int bottomCellY = (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0); if (topBorder != Border.NONE) { bottomCellY++; } 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) // 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; } // 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(); } } } /** * 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; try { reader = new BufferedReader(new FileReader(csvFile)); String line = null; boolean first = true; for (line = reader.readLine(); line != null; line = reader.readLine()) { List list = StringUtils.fromCsv(line); if (list.size() == 0) { continue; } 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()); 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)); } } } finally { if (reader != null) { reader.close(); } } 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(); } } } /** * 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 { // Emit top-right corner. writer.write("\u2510"); } } cellI++; } } writer.write("\n"); 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) ) { 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"); } } } 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"); } 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"); } cellI++; } writer.write("\n"); rowI++; } } finally { if (writer != null) { writer.close(); } } } /** * 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); } /** * Get the text of a particular cell. * * @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 setBorderRowBelowThick() { rows.get(selectedRow).bottomBorder = Border.THICK; rows.get(selectedRow).height = 2; alignGrid(); } }