stubs for TTableWidget
[fanfix.git] / src / jexer / TTableWidget.java
diff --git a/src/jexer/TTableWidget.java b/src/jexer/TTableWidget.java
new file mode 100644 (file)
index 0000000..480e828
--- /dev/null
@@ -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<Column> columns = new ArrayList<Column>();
+
+    /**
+     * The underlying data, organized as rows.
+     */
+    private ArrayList<Row> rows = new ArrayList<Row>();
+
+    /**
+     * 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<Cell> cells = new ArrayList<Cell>();
+
+        /**
+         * 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<Cell> cells = new ArrayList<Cell>();
+
+        /**
+         * 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);
+    }
+
+}