From: Kevin Lamonte Date: Fri, 8 Mar 2019 14:08:21 +0000 (-0600) Subject: stubs for TTableWidget X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=1dac6b8d395e2bf3c1b58915f8f4f481d9e46793;p=fanfix-jexer.git stubs for TTableWidget --- diff --git a/.gitignore b/.gitignore index 7c3b76b..9e8f22f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ build/** # Generated docs docs/** +# Maven artifacts +target/** + # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 264b8c8..872266d 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -62,6 +62,7 @@ import jexer.backend.ECMA48Backend; import jexer.backend.TWindowBackend; import jexer.menu.TMenu; import jexer.menu.TMenuItem; +import jexer.menu.TSubMenu; import static jexer.TCommand.*; import static jexer.TKeypress.*; @@ -3094,6 +3095,50 @@ public class TApplication implements Runnable { return helpMenu; } + /** + * Convenience function to add a default "Table" menu. + * + * @return the new menu + */ + public final TMenu addTableMenu() { + TMenu tableMenu = addMenu(i18n.getString("tableMenuTitle")); + TSubMenu borderMenu = tableMenu.addSubMenu(i18n. + getString("tableSubMenuBorders")); + borderMenu.addDefaultItem(TMenu.MID_TABLE_BORDER_NONE, false); + borderMenu.addDefaultItem(TMenu.MID_TABLE_BORDER_ALL, false); + borderMenu.addDefaultItem(TMenu.MID_TABLE_BORDER_RIGHT, false); + borderMenu.addDefaultItem(TMenu.MID_TABLE_BORDER_LEFT, false); + borderMenu.addDefaultItem(TMenu.MID_TABLE_BORDER_TOP, false); + borderMenu.addDefaultItem(TMenu.MID_TABLE_BORDER_BOTTOM, false); + borderMenu.addDefaultItem(TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM, false); + borderMenu.addDefaultItem(TMenu.MID_TABLE_BORDER_THICK_BOTTOM, false); + TSubMenu deleteMenu = tableMenu.addSubMenu(i18n. + getString("tableSubMenuDelete")); + deleteMenu.addDefaultItem(TMenu.MID_TABLE_DELETE_LEFT, false); + deleteMenu.addDefaultItem(TMenu.MID_TABLE_DELETE_UP, false); + deleteMenu.addDefaultItem(TMenu.MID_TABLE_DELETE_ROW, false); + deleteMenu.addDefaultItem(TMenu.MID_TABLE_DELETE_COLUMN, false); + TSubMenu insertMenu = tableMenu.addSubMenu(i18n. + getString("tableSubMenuInsert")); + insertMenu.addDefaultItem(TMenu.MID_TABLE_INSERT_LEFT, false); + insertMenu.addDefaultItem(TMenu.MID_TABLE_INSERT_RIGHT, false); + insertMenu.addDefaultItem(TMenu.MID_TABLE_INSERT_ABOVE, false); + insertMenu.addDefaultItem(TMenu.MID_TABLE_INSERT_BELOW, false); + TSubMenu columnMenu = tableMenu.addSubMenu(i18n. + getString("tableSubMenuColumn")); + columnMenu.addDefaultItem(TMenu.MID_TABLE_COLUMN_NARROW, false); + columnMenu.addDefaultItem(TMenu.MID_TABLE_COLUMN_WIDEN, false); + TSubMenu fileMenu = tableMenu.addSubMenu(i18n. + getString("tableSubMenuFile")); + fileMenu.addDefaultItem(TMenu.MID_TABLE_FILE_SAVE_CSV, false); + fileMenu.addDefaultItem(TMenu.MID_TABLE_FILE_SAVE_TEXT, false); + + TStatusBar statusBar = tableMenu.newStatusBar(i18n. + getString("tableMenuStatus")); + statusBar.addShortcutKeypress(kbF1, cmHelp, i18n.getString("Help")); + return tableMenu; + } + // ------------------------------------------------------------------------ // TTimer management ------------------------------------------------------ // ------------------------------------------------------------------------ diff --git a/src/jexer/TApplication.properties b/src/jexer/TApplication.properties index c4d183e..5c50457 100644 --- a/src/jexer/TApplication.properties +++ b/src/jexer/TApplication.properties @@ -11,6 +11,14 @@ windowMenuStatus=Open, arrange, and list windows helpMenuTitle=&Help helpMenuStatus=Access online help +tableMenuTitle=&Table +tableSubMenuBorders=&Borders +tableSubMenuDelete=&Delete +tableSubMenuInsert=&Insert +tableSubMenuColumn=&Column +tableSubMenuFile=&File +tableMenuStatus=Table manipulation commands + exitDialogTitle=Confirmation exitDialogText=Exit application? diff --git a/src/jexer/TField.java b/src/jexer/TField.java index b4330b4..e8fd3de 100644 --- a/src/jexer/TField.java +++ b/src/jexer/TField.java @@ -43,6 +43,11 @@ public class TField extends TWidget { // Variables -------------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Background character for unfilled-in text. + */ + protected char backgroundChar = GraphicsChars.HATCH; + /** * Field text. */ @@ -84,6 +89,16 @@ public class TField extends TWidget { */ protected TAction updateAction; + /** + * The color to use when this field is active. + */ + private String activeColorKey = "tfield.active"; + + /** + * The color to use when this field is not active. + */ + private String inactiveColorKey = "tfield.inactive"; + // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -349,16 +364,16 @@ public class TField extends TWidget { CellAttributes fieldColor; if (isAbsoluteActive()) { - fieldColor = getTheme().getColor("tfield.active"); + fieldColor = getTheme().getColor(activeColorKey); } else { - fieldColor = getTheme().getColor("tfield.inactive"); + fieldColor = getTheme().getColor(inactiveColorKey); } int end = windowStart + getWidth(); if (end > text.length()) { end = text.length(); } - hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor); + hLineXY(0, 0, getWidth(), backgroundChar, fieldColor); putStringXY(0, 0, text.substring(windowStart, end), fieldColor); // Fix the cursor, it will be rendered by TApplication.drawAll(). @@ -369,6 +384,24 @@ public class TField extends TWidget { // TField ----------------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Get field background character. + * + * @return background character + */ + public final char getBackgroundChar() { + return backgroundChar; + } + + /** + * Set field background character. + * + * @param backgroundChar the background character + */ + public void setBackgroundChar(final char backgroundChar) { + this.backgroundChar = backgroundChar; + } + /** * Get field text. * @@ -520,4 +553,25 @@ public class TField extends TWidget { normalizeWindowStart(); } + /** + * Set the active color key. + * + * @param activeColorKey ColorTheme key color to use when this field is + * active + */ + public void setActiveColorKey(final String activeColorKey) { + this.activeColorKey = activeColorKey; + } + + /** + * Set the inactive color key. + * + * @param inactiveColorKey ColorTheme key color to use when this field is + * inactive + */ + public void setInactiveColorKey(final String inactiveColorKey) { + this.inactiveColorKey = inactiveColorKey; + } + + } diff --git a/src/jexer/TKeypress.java b/src/jexer/TKeypress.java index 9cb4932..7713e55 100644 --- a/src/jexer/TKeypress.java +++ b/src/jexer/TKeypress.java @@ -821,6 +821,13 @@ public class TKeypress { return "\u25C0\u2500\u2518"; } + if (equals(kbShiftLeft)) { + return "Shift+\u2190"; + } + if (equals(kbShiftRight)) { + return "Shift+\u2192"; + } + if (isFunctionKey) { switch (keyCode) { case F1: diff --git a/src/jexer/TLabel.java b/src/jexer/TLabel.java index f33a8d7..75623ff 100644 --- a/src/jexer/TLabel.java +++ b/src/jexer/TLabel.java @@ -222,6 +222,24 @@ public class TLabel extends TWidget { mnemonic = new MnemonicString(label); } + /** + * Get the label color. + * + * @param return the ColorTheme key color to use for foreground text + */ + public String getColorKey() { + return colorKey; + } + + /** + * Set the label color. + * + * @param colorKey ColorTheme key color to use for foreground text + */ + public void setColorKey(final String colorKey) { + this.colorKey = colorKey; + } + /** * Act as though the mnemonic shortcut was pressed. */ diff --git a/src/jexer/TPasswordField.java b/src/jexer/TPasswordField.java index ea88dfa..1b78fc8 100644 --- a/src/jexer/TPasswordField.java +++ b/src/jexer/TPasswordField.java @@ -115,7 +115,7 @@ public class TPasswordField extends TField { end = text.length(); } - hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor); + hLineXY(0, 0, getWidth(), backgroundChar, fieldColor); if (showStars) { hLineXY(0, 0, getWidth() - 2, '*', fieldColor); } else { 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); + } + +} diff --git a/src/jexer/TTableWindow.java b/src/jexer/TTableWindow.java new file mode 100644 index 0000000..0219cfc --- /dev/null +++ b/src/jexer/TTableWindow.java @@ -0,0 +1,328 @@ +/* + * 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.ResourceBundle; + +import jexer.event.TCommandEvent; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import jexer.event.TResizeEvent; +import jexer.menu.TMenu; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; + +/** + * TTableWindow is used to display and edit regular two-dimensional tables of + * cells. + */ +public class TTableWindow extends TScrollableWindow { + + /** + * Translated strings. + */ + private static final ResourceBundle i18n = ResourceBundle.getBundle(TTableWindow.class.getName()); + + // ------------------------------------------------------------------------ + // Variables -------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * The table widget. + */ + private TTableWidget tableField; + + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Public constructor sets window title. + * + * @param parent the main application + * @param title the window title + */ + public TTableWindow(final TApplication parent, final String title) { + + super(parent, title, 0, 0, parent.getScreen().getWidth() / 2, + parent.getScreen().getHeight() / 2 - 2, RESIZABLE | CENTERED); + + tableField = new TTableWidget(this, 0, 0, getWidth() - 2, getHeight() - 2); + setupAfterTable(); + } + + // ------------------------------------------------------------------------ + // Event handlers --------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Called by application.switchWindow() when this window gets the + * focus, and also by application.addWindow(). + */ + public void onFocus() { + // Enable the table menu items. + getApplication().enableMenuItem(TMenu.MID_CUT); + getApplication().enableMenuItem(TMenu.MID_TABLE_BORDER_NONE); + getApplication().enableMenuItem(TMenu.MID_TABLE_BORDER_ALL); + getApplication().enableMenuItem(TMenu.MID_TABLE_BORDER_RIGHT); + getApplication().enableMenuItem(TMenu.MID_TABLE_BORDER_LEFT); + getApplication().enableMenuItem(TMenu.MID_TABLE_BORDER_TOP); + getApplication().enableMenuItem(TMenu.MID_TABLE_BORDER_BOTTOM); + getApplication().enableMenuItem(TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM); + getApplication().enableMenuItem(TMenu.MID_TABLE_BORDER_THICK_BOTTOM); + getApplication().enableMenuItem(TMenu.MID_TABLE_DELETE_LEFT); + getApplication().enableMenuItem(TMenu.MID_TABLE_DELETE_UP); + getApplication().enableMenuItem(TMenu.MID_TABLE_DELETE_ROW); + getApplication().enableMenuItem(TMenu.MID_TABLE_DELETE_COLUMN); + getApplication().enableMenuItem(TMenu.MID_TABLE_INSERT_LEFT); + getApplication().enableMenuItem(TMenu.MID_TABLE_INSERT_RIGHT); + getApplication().enableMenuItem(TMenu.MID_TABLE_INSERT_ABOVE); + getApplication().enableMenuItem(TMenu.MID_TABLE_INSERT_BELOW); + getApplication().enableMenuItem(TMenu.MID_TABLE_COLUMN_NARROW); + getApplication().enableMenuItem(TMenu.MID_TABLE_COLUMN_WIDEN); + getApplication().enableMenuItem(TMenu.MID_TABLE_FILE_SAVE_CSV); + getApplication().enableMenuItem(TMenu.MID_TABLE_FILE_SAVE_TEXT); + } + + /** + * Called by application.switchWindow() when another window gets the + * focus. + */ + public void onUnfocus() { + // Disable the table menu items. + getApplication().disableMenuItem(TMenu.MID_CUT); + getApplication().disableMenuItem(TMenu.MID_TABLE_BORDER_NONE); + getApplication().disableMenuItem(TMenu.MID_TABLE_BORDER_ALL); + getApplication().disableMenuItem(TMenu.MID_TABLE_BORDER_RIGHT); + getApplication().disableMenuItem(TMenu.MID_TABLE_BORDER_LEFT); + getApplication().disableMenuItem(TMenu.MID_TABLE_BORDER_TOP); + getApplication().disableMenuItem(TMenu.MID_TABLE_BORDER_BOTTOM); + getApplication().disableMenuItem(TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM); + getApplication().disableMenuItem(TMenu.MID_TABLE_BORDER_THICK_BOTTOM); + getApplication().disableMenuItem(TMenu.MID_TABLE_DELETE_LEFT); + getApplication().disableMenuItem(TMenu.MID_TABLE_DELETE_UP); + getApplication().disableMenuItem(TMenu.MID_TABLE_DELETE_ROW); + getApplication().disableMenuItem(TMenu.MID_TABLE_DELETE_COLUMN); + getApplication().disableMenuItem(TMenu.MID_TABLE_INSERT_LEFT); + getApplication().disableMenuItem(TMenu.MID_TABLE_INSERT_RIGHT); + getApplication().disableMenuItem(TMenu.MID_TABLE_INSERT_ABOVE); + getApplication().disableMenuItem(TMenu.MID_TABLE_INSERT_BELOW); + getApplication().disableMenuItem(TMenu.MID_TABLE_COLUMN_NARROW); + getApplication().disableMenuItem(TMenu.MID_TABLE_COLUMN_WIDEN); + getApplication().disableMenuItem(TMenu.MID_TABLE_FILE_SAVE_CSV); + getApplication().disableMenuItem(TMenu.MID_TABLE_FILE_SAVE_TEXT); + } + + // ------------------------------------------------------------------------ + // TWindow ---------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Draw the window. + */ + @Override + public void draw() { + // Draw as normal. + super.draw(); + + // Add borders on rows and columns. + // TODO + } + + /** + * 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 (mouseOnTable(mouse)) { + // The table might have changed, update the scollbars. + // TODO + /* + setBottomValue(editField.getMaximumRowNumber()); + setVerticalValue(editField.getVisibleRowNumber()); + setRightValue(editField.getMaximumColumnNumber()); + setHorizontalValue(editField.getEditingColumnNumber()); + */ + } else { + if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) { + // Vertical scrollbar actions + // TODO + // editField.setVisibleRowNumber(getVerticalValue()); + } + } + } + + /** + * 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.onMouseUp(mouse); + + if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) { + // Clicked on vertical scrollbar + // TODO + // editField.setVisibleRowNumber(getVerticalValue()); + } + + // TODO: horizontal scrolling + } + + /** + * Method that subclasses can override to handle mouse movements. + * + * @param mouse mouse motion event + */ + @Override + public void onMouseMotion(final TMouseEvent mouse) { + // Use TWidget's code to pass the event to the children. + super.onMouseMotion(mouse); + + if (mouseOnTable(mouse) && mouse.isMouse1()) { + // The editor might have changed, update the scollbars. + // TODO + /* + setBottomValue(editField.getMaximumRowNumber()); + setVerticalValue(editField.getVisibleRowNumber()); + setRightValue(editField.getMaximumColumnNumber()); + setHorizontalValue(editField.getEditingColumnNumber()); + */ + } else { + if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) { + // Clicked/dragged on vertical scrollbar + // TODO + // editField.setVisibleRowNumber(getVerticalValue()); + } + + // TODO: horizontal scrolling + } + + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + // Use TWidget's code to pass the event to the children. + super.onKeypress(keypress); + + // The editor might have changed, update the scollbars. + // TODO + /* + setBottomValue(editField.getMaximumRowNumber()); + setVerticalValue(editField.getVisibleRowNumber()); + setRightValue(editField.getMaximumColumnNumber()); + setHorizontalValue(editField.getEditingColumnNumber()); + */ + } + + /** + * Handle window/screen resize events. + * + * @param event resize event + */ + @Override + public void onResize(final TResizeEvent event) { + if (event.getType() == TResizeEvent.Type.WIDGET) { + // Resize the table + TResizeEvent tableSize = new TResizeEvent(TResizeEvent.Type.WIDGET, + event.getWidth() - 2, event.getHeight() - 2); + tableField.onResize(tableSize); + + // Have TScrollableWindow handle the scrollbars + super.onResize(event); + return; + } + + // Pass to children instead + for (TWidget widget: getChildren()) { + widget.onResize(event); + } + } + + // ------------------------------------------------------------------------ + // TTableWindow ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Setup other fields after the table is created. + */ + private void setupAfterTable() { + hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20); + vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2); + setMinimumWindowWidth(25); + setMinimumWindowHeight(10); + setTopValue(1); + // setBottomValue(editField.getMaximumRowNumber()); + setLeftValue(1); + // setRightValue(editField.getMaximumColumnNumber()); + + statusBar = newStatusBar(i18n.getString("statusBar")); + statusBar.addShortcutKeypress(kbF1, cmHelp, + i18n.getString("statusBarHelp")); + /* + statusBar.addShortcutKeypress(kbF2, cmSave, + i18n.getString("statusBarSave")); + statusBar.addShortcutKeypress(kbF3, cmOpen, + i18n.getString("statusBarOpen")); + */ + statusBar.addShortcutKeypress(kbF10, cmMenu, + i18n.getString("statusBarMenu")); + } + + /** + * Check if a mouse press/release/motion event coordinate is over the + * table. + * + * @param mouse a mouse-based event + * @return whether or not the mouse is on the table + */ + private boolean mouseOnTable(final TMouseEvent mouse) { + if ((mouse.getAbsoluteX() >= getAbsoluteX() + 1) + && (mouse.getAbsoluteX() < getAbsoluteX() + getWidth() - 1) + && (mouse.getAbsoluteY() >= getAbsoluteY() + 1) + && (mouse.getAbsoluteY() < getAbsoluteY() + getHeight() - 1) + ) { + return true; + } + return false; + } + +} diff --git a/src/jexer/TTableWindow.properties b/src/jexer/TTableWindow.properties new file mode 100644 index 0000000..a93ecdd --- /dev/null +++ b/src/jexer/TTableWindow.properties @@ -0,0 +1,3 @@ +statusBar=Editor +statusBarHelp=Help +statusBarMenu=Menu diff --git a/src/jexer/bits/ColorTheme.java b/src/jexer/bits/ColorTheme.java index 9ac4986..5109dde 100644 --- a/src/jexer/bits/ColorTheme.java +++ b/src/jexer/bits/ColorTheme.java @@ -634,6 +634,18 @@ public class ColorTheme { color.setBold(false); colors.put("teditor", color); + // TTable + color = new CellAttributes(); + color.setForeColor(Color.WHITE); + color.setBackColor(Color.BLUE); + color.setBold(false); + colors.put("ttable.inactive", color); + color = new CellAttributes(); + color.setForeColor(Color.BLACK); + color.setBackColor(Color.CYAN); + color.setBold(false); + colors.put("ttable.active", color); + } /** diff --git a/src/jexer/demos/Demo2.java b/src/jexer/demos/Demo2.java index e6112f6..2db03ce 100644 --- a/src/jexer/demos/Demo2.java +++ b/src/jexer/demos/Demo2.java @@ -68,11 +68,20 @@ public class Demo2 { format(i18n.getString("newConnection"), socket)); DemoApplication app = new DemoApplication(socket.getInputStream(), socket.getOutputStream()); + (new Thread(app)).start(); + Thread.sleep(500); + System.out.println(MessageFormat. + format(i18n.getString("terminal"), + ((jexer.net.TelnetInputStream) socket.getInputStream()). + getTerminalType())); + System.out.println(MessageFormat. + format(i18n.getString("username"), + ((jexer.net.TelnetInputStream) socket.getInputStream()). + getUsername())); System.out.println(MessageFormat. format(i18n.getString("language"), ((jexer.net.TelnetInputStream) socket.getInputStream()). getLanguage())); - (new Thread(app)).start(); } } catch (Exception e) { e.printStackTrace(); diff --git a/src/jexer/demos/Demo2.properties b/src/jexer/demos/Demo2.properties index 0d93747..fa2b98f 100644 --- a/src/jexer/demos/Demo2.properties +++ b/src/jexer/demos/Demo2.properties @@ -1,3 +1,5 @@ usageString=USAGE: java -cp jexer.jar jexer.demos.Demo2 port newConnection=New connection: {0} +username=\ \ \ username: {0} language=\ \ \ language: {0} +terminal=\ \ \ terminal: {0} diff --git a/src/jexer/demos/DemoApplication.java b/src/jexer/demos/DemoApplication.java index 0e4f189..8505703 100644 --- a/src/jexer/demos/DemoApplication.java +++ b/src/jexer/demos/DemoApplication.java @@ -236,6 +236,7 @@ public class DemoApplication extends TApplication { item = swingMenu.addItem(3001, i18n.getString("smaller")); } + addTableMenu(); addWindowMenu(); addHelpMenu(); } diff --git a/src/jexer/demos/DemoMainWindow.java b/src/jexer/demos/DemoMainWindow.java index 1ae35f0..5433d44 100644 --- a/src/jexer/demos/DemoMainWindow.java +++ b/src/jexer/demos/DemoMainWindow.java @@ -42,6 +42,7 @@ import jexer.TEditColorThemeWindow; import jexer.TEditorWindow; import jexer.TLabel; import jexer.TProgressBar; +import jexer.TTableWindow; import jexer.TTimer; import jexer.TWidget; import jexer.TWindow; @@ -119,7 +120,7 @@ public class DemoMainWindow extends TWindow { private DemoMainWindow(final TApplication parent, final int flags) { // Construct a demo window. X and Y don't matter because it will be // centered on screen. - super(parent, i18n.getString("windowTitle"), 0, 0, 64, 23, flags); + super(parent, i18n.getString("windowTitle"), 0, 0, 64, 25, flags); int row = 1; @@ -191,6 +192,24 @@ public class DemoMainWindow extends TWindow { ); row += 2; + addLabel(i18n.getString("ttableLabel"), 1, row); + addButton(i18n.getString("ttableButton1"), 35, row, + new TAction() { + public void DO() { + // TODO + } + } + ); + addButton(i18n.getString("ttableButton2"), 48, row, + new TAction() { + public void DO() { + new TTableWindow(getApplication(), + i18n.getString("tableDemo")); + } + } + ); + row += 2; + addLabel(i18n.getString("treeViewLabel"), 1, row); addButton(i18n.getString("treeViewButton"), 35, row, new TAction() { diff --git a/src/jexer/demos/DemoMainWindow.properties b/src/jexer/demos/DemoMainWindow.properties index 4a512cb..31f276c 100644 --- a/src/jexer/demos/DemoMainWindow.properties +++ b/src/jexer/demos/DemoMainWindow.properties @@ -17,6 +17,9 @@ radioButtonButton=&CheckBoxes editorLabel=Editor window editorButton1=&1 Widget editorButton2=&2 Window +ttableLabel=TTable +ttableButton1=&3 Widget +ttableButton2=&4 Window textAreaLabel=Text areas textAreaButton=&Text treeViewLabel=Tree views @@ -31,3 +34,5 @@ timerText=Timer: %d errorTitle=Error errorReadingFile=Error reading file: {0} errorOpeningFile=Error opening file dialog: {0} + +tableDemo=TTableWindow Demo diff --git a/src/jexer/menu/TMenu.java b/src/jexer/menu/TMenu.java index 7c31212..1bcc800 100644 --- a/src/jexer/menu/TMenu.java +++ b/src/jexer/menu/TMenu.java @@ -101,6 +101,28 @@ public class TMenu extends TWindow { public static final int MID_HELP_ACTIVE_FILE = 55; public static final int MID_ABOUT = 56; + // Table menu + public static final int MID_TABLE_BORDER_NONE = 60; + public static final int MID_TABLE_BORDER_ALL = 61; + public static final int MID_TABLE_BORDER_RIGHT = 62; + public static final int MID_TABLE_BORDER_LEFT = 63; + public static final int MID_TABLE_BORDER_TOP = 64; + public static final int MID_TABLE_BORDER_BOTTOM = 65; + public static final int MID_TABLE_BORDER_DOUBLE_BOTTOM = 66; + public static final int MID_TABLE_BORDER_THICK_BOTTOM = 67; + public static final int MID_TABLE_DELETE_LEFT = 68; + public static final int MID_TABLE_DELETE_UP = 69; + public static final int MID_TABLE_DELETE_ROW = 70; + public static final int MID_TABLE_DELETE_COLUMN = 71; + public static final int MID_TABLE_INSERT_LEFT = 72; + public static final int MID_TABLE_INSERT_RIGHT = 73; + public static final int MID_TABLE_INSERT_ABOVE = 74; + public static final int MID_TABLE_INSERT_BELOW = 75; + public static final int MID_TABLE_COLUMN_NARROW = 76; + public static final int MID_TABLE_COLUMN_WIDEN = 77; + public static final int MID_TABLE_FILE_SAVE_CSV = 78; + public static final int MID_TABLE_FILE_SAVE_TEXT = 79; + // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- // ------------------------------------------------------------------------ @@ -627,6 +649,69 @@ public class TMenu extends TWindow { label = i18n.getString("menuHelpAbout"); break; + case MID_TABLE_BORDER_NONE: + label = i18n.getString("menuTableBorderNone"); + break; + case MID_TABLE_BORDER_ALL: + label = i18n.getString("menuTableBorderAll"); + break; + case MID_TABLE_BORDER_RIGHT: + label = i18n.getString("menuTableBorderRight"); + break; + case MID_TABLE_BORDER_LEFT: + label = i18n.getString("menuTableBorderLeft"); + break; + case MID_TABLE_BORDER_TOP: + label = i18n.getString("menuTableBorderTop"); + break; + case MID_TABLE_BORDER_BOTTOM: + label = i18n.getString("menuTableBorderBottom"); + break; + case MID_TABLE_BORDER_DOUBLE_BOTTOM: + label = i18n.getString("menuTableBorderDoubleBottom"); + break; + case MID_TABLE_BORDER_THICK_BOTTOM: + label = i18n.getString("menuTableBorderThickBottom"); + break; + case MID_TABLE_DELETE_LEFT: + label = i18n.getString("menuTableDeleteLeft"); + break; + case MID_TABLE_DELETE_UP: + label = i18n.getString("menuTableDeleteUp"); + break; + case MID_TABLE_DELETE_ROW: + label = i18n.getString("menuTableDeleteRow"); + break; + case MID_TABLE_DELETE_COLUMN: + label = i18n.getString("menuTableDeleteColumn"); + break; + case MID_TABLE_INSERT_LEFT: + label = i18n.getString("menuTableInsertLeft"); + break; + case MID_TABLE_INSERT_RIGHT: + label = i18n.getString("menuTableInsertRight"); + break; + case MID_TABLE_INSERT_ABOVE: + label = i18n.getString("menuTableInsertAbove"); + break; + case MID_TABLE_INSERT_BELOW: + label = i18n.getString("menuTableInsertBelow"); + break; + case MID_TABLE_COLUMN_NARROW: + label = i18n.getString("menuTableColumnNarrow"); + key = kbShiftLeft; + break; + case MID_TABLE_COLUMN_WIDEN: + label = i18n.getString("menuTableColumnWiden"); + key = kbShiftRight; + break; + case MID_TABLE_FILE_SAVE_CSV: + label = i18n.getString("menuTableFileSaveCsv"); + break; + case MID_TABLE_FILE_SAVE_TEXT: + label = i18n.getString("menuTableFileSaveText"); + break; + default: throw new IllegalArgumentException("Invalid menu ID: " + id); } diff --git a/src/jexer/menu/TMenu.properties b/src/jexer/menu/TMenu.properties index 0ce4cde..c64da18 100644 --- a/src/jexer/menu/TMenu.properties +++ b/src/jexer/menu/TMenu.properties @@ -26,6 +26,27 @@ menuHelpHelp=&Help on help menuHelpActive=Active &file... menuHelpAbout=&About... +menuTableBorderNone=&None +menuTableBorderAll=&All +menuTableBorderRight=&Right +menuTableBorderLeft=&Left +menuTableBorderTop=&Top +menuTableBorderBottom=&Bottom +menuTableBorderDoubleBottom=Bottom (&double) +menuTableBorderThickBottom=Bottom (t&hick) +menuTableDeleteLeft=Cell (Shift &Left) +menuTableDeleteUp=Cell (Shift &Up) +menuTableDeleteRow=Entire &Row +menuTableDeleteColumn=Entire &Column +menuTableInsertLeft=Column &Left +menuTableInsertRight=Column &Right +menuTableInsertAbove=Row &Above +menuTableInsertBelow=Row &Below +menuTableColumnNarrow=&Narrow +menuTableColumnWiden=&Widen +menuTableFileSaveCsv=Save As &CSV... +menuTableFileSaveText=Save As &Text... + menuRepaintDesktop=&Repaint desktop menuViewImage=&Open image... menuChangeFont=Change &font... diff --git a/src/jexer/menu/TSubMenu.java b/src/jexer/menu/TSubMenu.java index 547711b..e285c5a 100644 --- a/src/jexer/menu/TSubMenu.java +++ b/src/jexer/menu/TSubMenu.java @@ -234,6 +234,18 @@ public class TSubMenu extends TMenuItem { return menu.addDefaultItem(id); } + /** + * Convenience function to add one of the default menu items. + * + * @param id menu item ID. Must be between 0 (inclusive) and 1023 + * (inclusive). + * @param enabled default state for enabled + * @return the new menu item + */ + public TMenuItem addDefaultItem(final int id, final boolean enabled) { + return menu.addDefaultItem(id, enabled); + } + /** * Convenience function to add a menu separator. */ diff --git a/src/jexer/net/TelnetInputStream.java b/src/jexer/net/TelnetInputStream.java index ac010e8..3e74561 100644 --- a/src/jexer/net/TelnetInputStream.java +++ b/src/jexer/net/TelnetInputStream.java @@ -199,6 +199,15 @@ public class TelnetInputStream extends InputStream implements SessionInfo { this.language = language; } + /** + * Get the terminal type as reported by the telnet Terminal Type option. + * + * @return the terminal type + */ + public String getTerminalType() { + return master.terminalType; + } + /** * Text window width getter. * @@ -311,6 +320,7 @@ public class TelnetInputStream extends InputStream implements SessionInfo { // If we got something, return it. if (rc > 0) { + readBufferEnd += rc; readBufferStart++; return readBuffer[readBufferStart - 1]; }