/*
 * This file is part of lanterna (http://code.google.com/p/lanterna/).
 *
 * lanterna is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2010-2020 Martin Berglund
 */
package com.googlecode.lanterna.gui2.table;

import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.input.KeyStroke;

import java.util.List;

The table class is an interactable component that displays a grid of cells containing data along with a header of labels. It supports scrolling when the number of rows and/or columns gets too large to fit and also supports user selection which is either row-based or cell-based. User will move the current selection by using the arrow keys on the keyboard.
Author:Martin
Type parameters:
  • <V> – Type of data to store in the table cells, presented through toString()
/** * The table class is an interactable component that displays a grid of cells containing data along with a header of * labels. It supports scrolling when the number of rows and/or columns gets too large to fit and also supports * user selection which is either row-based or cell-based. User will move the current selection by using the arrow keys * on the keyboard. * @param <V> Type of data to store in the table cells, presented through {@code toString()} * @author Martin */
public class Table<V> extends AbstractInteractableComponent<Table<V>> { private TableModel<V> tableModel; private TableModel.Listener<V> tableModelListener; // Used to invalidate the table whenever the model changes private TableHeaderRenderer<V> tableHeaderRenderer; private TableCellRenderer<V> tableCellRenderer; private Runnable selectAction; private boolean cellSelection; private int visibleRows; private int visibleColumns; private int viewTopRow; private int viewLeftColumn; private int selectedRow; private int selectedColumn; private boolean escapeByArrowKey;
Creates a new Table with the number of columns as specified by the array of labels
Params:
  • columnLabels – Creates one column per label in the array, must be more than one
/** * Creates a new {@code Table} with the number of columns as specified by the array of labels * @param columnLabels Creates one column per label in the array, must be more than one */
public Table(String... columnLabels) { if(columnLabels.length == 0) { throw new IllegalArgumentException("Table needs at least one column"); } this.tableHeaderRenderer = new DefaultTableHeaderRenderer<V>(); this.tableCellRenderer = new DefaultTableCellRenderer<V>(); this.tableModel = new TableModel<V>(columnLabels); this.selectAction = null; this.visibleColumns = 0; this.visibleRows = 0; this.viewTopRow = 0; this.viewLeftColumn = 0; this.cellSelection = false; this.selectedRow = 0; this.selectedColumn = -1; this.escapeByArrowKey = true; this.tableModelListener = new TableModel.Listener<V>() { @Override public void onRowAdded(TableModel<V> model, int index) { invalidate(); } @Override public void onRowRemoved(TableModel<V> model, int index, List<V> oldRow) { invalidate(); } @Override public void onColumnAdded(TableModel<V> model, int index) { invalidate(); } @Override public void onColumnRemoved(TableModel<V> model, int index, String oldHeader, List<V> oldColumn) { invalidate(); } @Override public void onCellChanged(TableModel<V> model, int row, int column, V oldValue, V newValue) { invalidate(); } }; this.tableModel.addListener(tableModelListener); }
Returns the underlying table model
Returns:Underlying table model
/** * Returns the underlying table model * @return Underlying table model */
public TableModel<V> getTableModel() { return tableModel; }
Updates the table with a new table model, effectively replacing the content of the table completely
Params:
  • tableModel – New table model
Returns:Itself
/** * Updates the table with a new table model, effectively replacing the content of the table completely * @param tableModel New table model * @return Itself */
public synchronized Table<V> setTableModel(TableModel<V> tableModel) { if(tableModel == null) { throw new IllegalArgumentException("Cannot assign a null TableModel"); } this.tableModel.removeListener(tableModelListener); this.tableModel = tableModel; this.tableModel.addListener(tableModelListener); invalidate(); return this; }
Returns the TableCellRenderer used by this table when drawing cells
Returns:TableCellRenderer used by this table when drawing cells
/** * Returns the {@code TableCellRenderer} used by this table when drawing cells * @return {@code TableCellRenderer} used by this table when drawing cells */
public TableCellRenderer<V> getTableCellRenderer() { return tableCellRenderer; }
Replaces the TableCellRenderer used by this table when drawing cells
Params:
  • tableCellRenderer – New TableCellRenderer to use
Returns:Itself
/** * Replaces the {@code TableCellRenderer} used by this table when drawing cells * @param tableCellRenderer New {@code TableCellRenderer} to use * @return Itself */
public synchronized Table<V> setTableCellRenderer(TableCellRenderer<V> tableCellRenderer) { this.tableCellRenderer = tableCellRenderer; invalidate(); return this; }
Returns the TableHeaderRenderer used by this table when drawing the table's header
Returns:TableHeaderRenderer used by this table when drawing the table's header
/** * Returns the {@code TableHeaderRenderer} used by this table when drawing the table's header * @return {@code TableHeaderRenderer} used by this table when drawing the table's header */
public TableHeaderRenderer<V> getTableHeaderRenderer() { return tableHeaderRenderer; }
Replaces the TableHeaderRenderer used by this table when drawing the table's header
Params:
  • tableHeaderRenderer – New TableHeaderRenderer to use
Returns:Itself
/** * Replaces the {@code TableHeaderRenderer} used by this table when drawing the table's header * @param tableHeaderRenderer New {@code TableHeaderRenderer} to use * @return Itself */
public synchronized Table<V> setTableHeaderRenderer(TableHeaderRenderer<V> tableHeaderRenderer) { this.tableHeaderRenderer = tableHeaderRenderer; invalidate(); return this; }
Sets the number of columns this table should show. If there are more columns in the table model, a scrollbar will be used to allow the user to scroll left and right and view all columns.
Params:
  • visibleColumns – Number of columns to display at once
/** * Sets the number of columns this table should show. If there are more columns in the table model, a scrollbar will * be used to allow the user to scroll left and right and view all columns. * @param visibleColumns Number of columns to display at once */
public synchronized void setVisibleColumns(int visibleColumns) { this.visibleColumns = visibleColumns; invalidate(); }
Returns the number of columns this table will show. If there are more columns in the table model, a scrollbar will be used to allow the user to scroll left and right and view all columns.
Returns:Number of visible columns for this table
/** * Returns the number of columns this table will show. If there are more columns in the table model, a scrollbar * will be used to allow the user to scroll left and right and view all columns. * @return Number of visible columns for this table */
public int getVisibleColumns() { return visibleColumns; }
Sets the number of rows this table will show. If there are more rows in the table model, a scrollbar will be used to allow the user to scroll up and down and view all rows.
Params:
  • visibleRows – Number of rows to display at once
/** * Sets the number of rows this table will show. If there are more rows in the table model, a scrollbar will be used * to allow the user to scroll up and down and view all rows. * @param visibleRows Number of rows to display at once */
public synchronized void setVisibleRows(int visibleRows) { this.visibleRows = visibleRows; invalidate(); }
Returns the number of rows this table will show. If there are more rows in the table model, a scrollbar will be used to allow the user to scroll up and down and view all rows.
Returns:Number of rows to display at once
/** * Returns the number of rows this table will show. If there are more rows in the table model, a scrollbar will be * used to allow the user to scroll up and down and view all rows. * @return Number of rows to display at once */
public int getVisibleRows() { return visibleRows; }
Returns the index of the row that is currently the first row visible. This is always 0 unless scrolling has been enabled and either the user or the software (through setViewTopRow(..)) has scrolled down.
Returns:Index of the row that is currently the first row visible
/** * Returns the index of the row that is currently the first row visible. This is always 0 unless scrolling has been * enabled and either the user or the software (through {@code setViewTopRow(..)}) has scrolled down. * @return Index of the row that is currently the first row visible */
public int getViewTopRow() { return viewTopRow; }
Sets the view row offset for the first row to display in the table. Calling this with 0 will make the first row in the model be the first visible row in the table.
Params:
  • viewTopRow – Index of the row that is currently the first row visible
Returns:Itself
/** * Sets the view row offset for the first row to display in the table. Calling this with 0 will make the first row * in the model be the first visible row in the table. * * @param viewTopRow Index of the row that is currently the first row visible * @return Itself */
public synchronized Table<V> setViewTopRow(int viewTopRow) { this.viewTopRow = viewTopRow; return this; }
Returns the index of the column that is currently the first column visible. This is always 0 unless scrolling has been enabled and either the user or the software (through setViewLeftColumn(..)) has scrolled to the right.
Returns:Index of the column that is currently the first column visible
/** * Returns the index of the column that is currently the first column visible. This is always 0 unless scrolling has * been enabled and either the user or the software (through {@code setViewLeftColumn(..)}) has scrolled to the * right. * @return Index of the column that is currently the first column visible */
public int getViewLeftColumn() { return viewLeftColumn; }
Sets the view column offset for the first column to display in the table. Calling this with 0 will make the first column in the model be the first visible column in the table.
Params:
  • viewLeftColumn – Index of the column that is currently the first column visible
Returns:Itself
/** * Sets the view column offset for the first column to display in the table. Calling this with 0 will make the first * column in the model be the first visible column in the table. * * @param viewLeftColumn Index of the column that is currently the first column visible * @return Itself */
public synchronized Table<V> setViewLeftColumn(int viewLeftColumn) { this.viewLeftColumn = viewLeftColumn; return this; }
Returns the currently selection column index, if in cell-selection mode. Otherwise it returns -1.
Returns:In cell-selection mode returns the index of the selected column, otherwise -1
/** * Returns the currently selection column index, if in cell-selection mode. Otherwise it returns -1. * @return In cell-selection mode returns the index of the selected column, otherwise -1 */
public int getSelectedColumn() { return selectedColumn; }
If in cell selection mode, updates which column is selected and ensures the selected column is visible in the view. If not in cell selection mode, does nothing.
Params:
  • selectedColumn – Index of the column that should be selected
Returns:Itself
/** * If in cell selection mode, updates which column is selected and ensures the selected column is visible in the * view. If not in cell selection mode, does nothing. * @param selectedColumn Index of the column that should be selected * @return Itself */
public synchronized Table<V> setSelectedColumn(int selectedColumn) { if(cellSelection) { this.selectedColumn = selectedColumn; ensureSelectedItemIsVisible(); } return this; }
Returns the index of the currently selected row
Returns:Index of the currently selected row
/** * Returns the index of the currently selected row * @return Index of the currently selected row */
public int getSelectedRow() { return selectedRow; }
Sets the index of the selected row and ensures the selected row is visible in the view
Params:
  • selectedRow – Index of the row to select
Returns:Itself
/** * Sets the index of the selected row and ensures the selected row is visible in the view * @param selectedRow Index of the row to select * @return Itself */
public synchronized Table<V> setSelectedRow(int selectedRow) { this.selectedRow = selectedRow; ensureSelectedItemIsVisible(); return this; }
If true, the user will be able to select and navigate individual cells, otherwise the user can only select full rows.
Params:
  • cellSelection – true if cell selection should be enabled, false for row selection
Returns:Itself
/** * If {@code true}, the user will be able to select and navigate individual cells, otherwise the user can only * select full rows. * @param cellSelection {@code true} if cell selection should be enabled, {@code false} for row selection * @return Itself */
public synchronized Table<V> setCellSelection(boolean cellSelection) { this.cellSelection = cellSelection; if(cellSelection && selectedColumn == -1) { selectedColumn = 0; } else if(!cellSelection) { selectedColumn = -1; } return this; }
Returns true if this table is in cell-selection mode, otherwise false
Returns:true if this table is in cell-selection mode, otherwise false
/** * Returns {@code true} if this table is in cell-selection mode, otherwise {@code false} * @return {@code true} if this table is in cell-selection mode, otherwise {@code false} */
public boolean isCellSelection() { return cellSelection; }
Assigns an action to run whenever the user presses the enter key while focused on the table. If called with null, no action will be run.
Params:
  • selectAction – Action to perform when user presses the enter key
Returns:Itself
/** * Assigns an action to run whenever the user presses the enter key while focused on the table. If called with * {@code null}, no action will be run. * @param selectAction Action to perform when user presses the enter key * @return Itself */
public synchronized Table<V> setSelectAction(Runnable selectAction) { this.selectAction = selectAction; return this; }
Returns true if this table can be navigated away from when the selected row is at one of the extremes and the user presses the array key to continue in that direction. With escapeByArrowKey set to true, this will move focus away from the table in the direction the user pressed, if false then nothing will happen.
Returns:true if user can switch focus away from the table using arrow keys, false otherwise
/** * Returns {@code true} if this table can be navigated away from when the selected row is at one of the extremes and * the user presses the array key to continue in that direction. With {@code escapeByArrowKey} set to {@code true}, * this will move focus away from the table in the direction the user pressed, if {@code false} then nothing will * happen. * @return {@code true} if user can switch focus away from the table using arrow keys, {@code false} otherwise */
public boolean isEscapeByArrowKey() { return escapeByArrowKey; }
Sets the flag for if this table can be navigated away from when the selected row is at one of the extremes and the user presses the array key to continue in that direction. With escapeByArrowKey set to true, this will move focus away from the table in the direction the user pressed, if false then nothing will happen.
Params:
  • escapeByArrowKey – true if user can switch focus away from the table using arrow keys, false otherwise
Returns:Itself
/** * Sets the flag for if this table can be navigated away from when the selected row is at one of the extremes and * the user presses the array key to continue in that direction. With {@code escapeByArrowKey} set to {@code true}, * this will move focus away from the table in the direction the user pressed, if {@code false} then nothing will * happen. * @param escapeByArrowKey {@code true} if user can switch focus away from the table using arrow keys, {@code false} otherwise * @return Itself */
public synchronized Table<V> setEscapeByArrowKey(boolean escapeByArrowKey) { this.escapeByArrowKey = escapeByArrowKey; return this; } @Override protected TableRenderer<V> createDefaultRenderer() { return new DefaultTableRenderer<V>(); } @Override public TableRenderer<V> getRenderer() { return (TableRenderer<V>)super.getRenderer(); } @Override public Result handleKeyStroke(KeyStroke keyStroke) { switch(keyStroke.getKeyType()) { case ArrowUp: if(selectedRow > 0) { selectedRow--; } else if(escapeByArrowKey) { return Result.MOVE_FOCUS_UP; } break; case ArrowDown: if(selectedRow < tableModel.getRowCount() - 1) { selectedRow++; } else if(escapeByArrowKey) { return Result.MOVE_FOCUS_DOWN; } break; case PageUp: if(visibleRows > 0 && selectedRow > 0) { selectedRow -= Math.min(getVisibleRows(), selectedRow); viewTopRow = selectedRow; } break; case PageDown: if(visibleRows > 0 && selectedRow < tableModel.getRowCount() - 1) { int toEndDistance = tableModel.getRowCount() - 1 - selectedRow; selectedRow += Math.min(getVisibleRows(), toEndDistance); viewTopRow = Math.min(selectedRow, toEndDistance); } break; case Home: selectedRow = 0; break; case End: selectedRow = tableModel.getRowCount() - 1; break; case ArrowLeft: if(cellSelection && selectedColumn > 0) { selectedColumn--; } else if(escapeByArrowKey) { return Result.MOVE_FOCUS_LEFT; } break; case ArrowRight: if(cellSelection && selectedColumn < tableModel.getColumnCount() - 1) { selectedColumn++; } else if(escapeByArrowKey) { return Result.MOVE_FOCUS_RIGHT; } break; case Enter: Runnable runnable = selectAction; //To avoid synchronizing if(runnable != null) { runnable.run(); } else { return Result.MOVE_FOCUS_NEXT; } break; default: return super.handleKeyStroke(keyStroke); } ensureSelectedItemIsVisible(); invalidate(); return Result.HANDLED; } private void ensureSelectedItemIsVisible() { if(visibleRows > 0 && selectedRow < viewTopRow) { viewTopRow = selectedRow; } else if(visibleRows > 0 && selectedRow >= viewTopRow + visibleRows) { viewTopRow = Math.max(0, selectedRow - visibleRows + 1); } if(selectedColumn != -1) { if(visibleColumns > 0 && selectedColumn < viewLeftColumn) { viewLeftColumn = selectedColumn; } else if(visibleColumns > 0 && selectedColumn >= viewLeftColumn + visibleColumns) { viewLeftColumn = Math.max(0, selectedColumn - visibleColumns + 1); } } } }