/* * 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.IOException; import java.util.ArrayList; import java.util.List; import jexer.bits.CellAttributes; import jexer.event.TKeypressEvent; import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.menu.TMenu; 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: \u258C (vertical, left half block) and \u2580 * (horizontal, upper block). */ THICK, } /** * 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 = 10; /** * Extra columns to add. */ private static final int EXTRA_COLUMNS = 10 * (8 + 1); // ------------------------------------------------------------------------ // 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) { StringBuilder sb = new StringBuilder(); for (;;) { sb.append((char) ('A' + (col % 26))); if (col < 26) { break; } col /= 26; } label = sb.reverse().toString(); } /** * 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; /** * 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 (isEditing) { if (keypress.equals(kbEsc)) { // ESC cancels the edit. field.setText(fieldText); isEditing = false; field.setEnabled(false); return; } if (keypress.equals(kbEnter)) { // Enter ends editing. 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); } } // ------------------------------------------------------------------------ // 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 */ public TTableWidget(final TWidget parent, final int x, final int y, final int width, final int height) { super(parent, x, y, width, height); // Initialize the starting row and column. rows.add(new Row(0)); columns.add(new Column(0)); // 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, 0, 0, /* j, i, */ columns.get(0).width, rows.get(0).height, column, row); // DEBUG: set a grid of cell index labels // TODO: remove this 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) ) { columns.add(new Column(column + 1)); } column++; } if (i + rows.get(0).height < height + EXTRA_ROWS) { 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) + (showRowLabels ? ROW_LABEL_WIDTH : 0)); } activate(columns.get(selectedColumn).get(selectedRow)); alignGrid(); // 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); } // ------------------------------------------------------------------------ // 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); alignGrid(); } /** * 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: if (selectedRow == 0) { topBorder = Border.NONE; } if (selectedColumn == 0) { leftBorder = Border.NONE; } columns.get(selectedColumn).rightBorder = Border.NONE; rows.get(selectedRow).bottomBorder = Border.NONE; rows.get(selectedRow).height = 1; break; case TMenu.MID_TABLE_BORDER_ALL: if (selectedRow == 0) { topBorder = Border.SINGLE; } if (selectedColumn == 0) { leftBorder = Border.SINGLE; } columns.get(selectedColumn).rightBorder = Border.SINGLE; rows.get(selectedRow).bottomBorder = Border.SINGLE; rows.get(selectedRow).height = 2; break; case TMenu.MID_TABLE_BORDER_RIGHT: columns.get(selectedColumn).rightBorder = Border.SINGLE; break; case TMenu.MID_TABLE_BORDER_LEFT: if (selectedColumn == 0) { leftBorder = Border.SINGLE; } else { columns.get(selectedColumn - 1).rightBorder = Border.SINGLE; } break; case TMenu.MID_TABLE_BORDER_TOP: if (selectedRow == 0) { topBorder = Border.SINGLE; } else { rows.get(selectedRow - 1).bottomBorder = Border.SINGLE; rows.get(selectedRow - 1).height = 2; } break; case TMenu.MID_TABLE_BORDER_BOTTOM: rows.get(selectedRow).bottomBorder = Border.SINGLE; rows.get(selectedRow).height = 2; break; case TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM: rows.get(selectedRow).bottomBorder = Border.DOUBLE; rows.get(selectedRow).height = 2; break; case TMenu.MID_TABLE_BORDER_THICK_BOTTOM: rows.get(selectedRow).bottomBorder = Border.THICK; rows.get(selectedRow).height = 2; break; 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++) { columns.get(i).setX(columns.get(i).getX() - 1); } 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++) { columns.get(i).setX(columns.get(i).getX() + 1); } break; case TMenu.MID_TABLE_FILE_SAVE_CSV: // TODO break; case TMenu.MID_TABLE_FILE_SAVE_TEXT: // TODO break; default: super.onMenu(menu); } // Fix/redraw the display. alignGrid(); } // ------------------------------------------------------------------------ // 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(), (topBorder == Border.NONE ? 0 : 1), 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), 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), getHeight(), '\u2502', borderColor); } } // Draw horizontal borders. if (topBorder == Border.SINGLE) { hLineXY((showRowLabels ? ROW_LABEL_WIDTH : 0), 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(), '\u2580', borderColor); } } // Top-left corner if needed if ((topBorder == Border.SINGLE) && (leftBorder == Border.SINGLE)) { putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0), 0, '\u250c', borderColor); } // TODO: draw the correct corners between rows and columns // Now draw the window borders. super.draw(); } // ------------------------------------------------------------------------ // TTable ----------------------------------------------------------------- // ------------------------------------------------------------------------ /** * 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 number of columns. * * @return the number of columns */ public int getColumnCount() { return columns.size(); } /** * Get the number of rows. * * @return the number of rows */ public int getRowCount() { return rows.size(); } /** * Align the grid so that the selected cell is fully visible. */ private void alignGrid() { 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; } } /** * Save contents to file. * * @param filename file to save to * @throws IOException if a java.io operation throws */ public void saveToFilename(final String filename) throws IOException { // TODO } }