X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTTableWidget.java;fp=src%2Fjexer%2FTTableWidget.java;h=480e8289c2e8def0f1be139675bb963544857c60;hb=1dac6b8d395e2bf3c1b58915f8f4f481d9e46793;hp=0000000000000000000000000000000000000000;hpb=2a92cf977ee2ae37d8302a294c6338fa51a5ca45;p=fanfix.git diff --git a/src/jexer/TTableWidget.java b/src/jexer/TTableWidget.java new file mode 100644 index 0000000..480e828 --- /dev/null +++ b/src/jexer/TTableWidget.java @@ -0,0 +1,586 @@ +/* + * 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.util.ArrayList; +import java.util.List; + +import jexer.event.TKeypressEvent; +import jexer.event.TMenuEvent; +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, + } + + // ------------------------------------------------------------------------ + // 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; + + /** + * Column represents a column of cells. + */ + public class Column { + + /** + * Width of column. + */ + private int width = 8; + + /** + * The cells of this column. + */ + private ArrayList cells = new ArrayList(); + + /** + * Column label. + */ + private String label = ""; + + /** + * The border for this column. + */ + private Border border = Border.NONE; + + /** + * 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); + } + } + + /** + * Row represents a row of cells. + */ + public class Row { + + /** + * Height of row. + */ + private int height = 1; + + /** + * The cells of this row. + */ + private ArrayList cells = new ArrayList(); + + /** + * Row label. + */ + private String label = ""; + + /** + * The border for this row. + */ + private Border border = Border.NONE; + + /** + * 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); + } + + } + + /** + * 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 - 1, false); + field.setEnabled(false); + field.setActiveColorKey("ttable.active"); + field.setInactiveColorKey("ttable.inactive"); + field.setBackgroundChar(' '); + } + + // -------------------------------------------------------------------- + // Event handlers ----------------------------------------------------- + // -------------------------------------------------------------------- + + /** + * 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(); + + field.setActiveColorKey("ttable.active"); + field.setInactiveColorKey("ttable.inactive"); + + if (isAbsoluteActive()) { + if (table.selectedColumn == column) { + if ((table.selectedRow == row) + || (table.highlightColumn == true) + ) { + field.setActiveColorKey("ttable.active"); + field.setInactiveColorKey("ttable.active"); + } + } else if (table.selectedRow == row) { + if ((table.selectedColumn == column) + || (table.highlightRow == true) + ) { + field.setActiveColorKey("ttable.active"); + field.setInactiveColorKey("ttable.active"); + } + } + } + + 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()); + columns.add(new Column()); + + // Place a grid of cells that fit in this space. + int row = 0; + for (int i = 0; i < height; i += rows.get(0).height) { + int column = 0; + for (int j = 0; j < width; 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); + rows.get(row).add(cell); + columns.get(column).add(cell); + if ((i == 0) && (j + columns.get(0).width < width)) { + columns.add(new Column()); + } + column++; + } + if (i + rows.get(0).height < height) { + rows.add(new Row()); + } + row++; + } + activate(columns.get(selectedColumn).get(selectedRow)); + } + + // ------------------------------------------------------------------------ + // Event handlers --------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * 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 (getSelectedCell().isEditing) { + super.onKeypress(keypress); + return; + } + + if (keypress.equals(kbLeft)) { + if (selectedColumn > 0) { + selectedColumn--; + } + activate(columns.get(selectedColumn).get(selectedRow)); + } else if (keypress.equals(kbRight)) { + if (selectedColumn < columns.size() - 1) { + selectedColumn++; + } + activate(columns.get(selectedColumn).get(selectedRow)); + } else if (keypress.equals(kbUp)) { + if (selectedRow > 0) { + selectedRow--; + } + activate(columns.get(selectedColumn).get(selectedRow)); + } else if (keypress.equals(kbDown)) { + if (selectedRow < rows.size() - 1) { + selectedRow++; + } + activate(columns.get(selectedColumn).get(selectedRow)); + } else if (keypress.equals(kbHome)) { + selectedColumn = 0; + activate(columns.get(selectedColumn).get(selectedRow)); + } else if (keypress.equals(kbEnd)) { + selectedColumn = columns.size() - 1; + activate(columns.get(selectedColumn).get(selectedRow)); + } else if (keypress.equals(kbPgUp)) { + // TODO + } else if (keypress.equals(kbPgDn)) { + // TODO + } else if (keypress.equals(kbCtrlHome)) { + // TODO + } else if (keypress.equals(kbCtrlEnd)) { + // TODO + } else { + // Pass to the Cell. + super.onKeypress(keypress); + } + } + + /** + * Handle posted menu events. + * + * @param menu menu event + */ + @Override + public void onMenu(final TMenuEvent menu) { + switch (menu.getId()) { + 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 - 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 - 1); + } + break; + case TMenu.MID_TABLE_FILE_SAVE_CSV: + case TMenu.MID_TABLE_FILE_SAVE_TEXT: + break; + default: + super.onMenu(menu); + } + } + + // ------------------------------------------------------------------------ + // TWidget ---------------------------------------------------------------- + // ------------------------------------------------------------------------ + + // ------------------------------------------------------------------------ + // 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); + } + +}