1 package com
.googlecode
.lanterna
.gui2
.table
;
3 import com
.googlecode
.lanterna
.gui2
.*;
4 import com
.googlecode
.lanterna
.input
.KeyStroke
;
7 * The table class is an interactable component that displays a grid of cells containing data along with a header of
8 * labels. It supports scrolling when the number of rows and/or columns gets too large to fit and also supports
9 * user selection which is either row-based or cell-based. User will move the current selection by using the arrow keys
11 * @param <V> Type of data to store in the table cells, presented through {@code toString()}
14 public class Table
<V
> extends AbstractInteractableComponent
<Table
<V
>> {
15 private TableModel
<V
> tableModel
;
16 private TableHeaderRenderer
<V
> tableHeaderRenderer
;
17 private TableCellRenderer
<V
> tableCellRenderer
;
18 private Runnable selectAction
;
19 private boolean cellSelection
;
20 private int visibleRows
;
21 private int visibleColumns
;
22 private int viewTopRow
;
23 private int viewLeftColumn
;
24 private int selectedRow
;
25 private int selectedColumn
;
26 private boolean escapeByArrowKey
;
29 * Creates a new {@code Table} with the number of columns as specified by the array of labels
30 * @param columnLabels Creates one column per label in the array, must be more than one
32 public Table(String
... columnLabels
) {
33 if(columnLabels
.length
== 0) {
34 throw new IllegalArgumentException("Table needs at least one column");
36 this.tableHeaderRenderer
= new DefaultTableHeaderRenderer
<V
>();
37 this.tableCellRenderer
= new DefaultTableCellRenderer
<V
>();
38 this.tableModel
= new TableModel
<V
>(columnLabels
);
39 this.selectAction
= null;
40 this.visibleColumns
= 0;
43 this.viewLeftColumn
= 0;
44 this.cellSelection
= false;
46 this.selectedColumn
= -1;
47 this.escapeByArrowKey
= true;
51 * Returns the underlying table model
52 * @return Underlying table model
54 public TableModel
<V
> getTableModel() {
59 * Updates the table with a new table model, effectively replacing the content of the table completely
60 * @param tableModel New table model
63 public synchronized Table
<V
> setTableModel(TableModel
<V
> tableModel
) {
64 if(tableModel
== null) {
65 throw new IllegalArgumentException("Cannot assign a null TableModel");
67 this.tableModel
= tableModel
;
73 * Returns the {@code TableCellRenderer} used by this table when drawing cells
74 * @return {@code TableCellRenderer} used by this table when drawing cells
76 public TableCellRenderer
<V
> getTableCellRenderer() {
77 return tableCellRenderer
;
81 * Replaces the {@code TableCellRenderer} used by this table when drawing cells
82 * @param tableCellRenderer New {@code TableCellRenderer} to use
85 public synchronized Table
<V
> setTableCellRenderer(TableCellRenderer
<V
> tableCellRenderer
) {
86 this.tableCellRenderer
= tableCellRenderer
;
92 * Returns the {@code TableHeaderRenderer} used by this table when drawing the table's header
93 * @return {@code TableHeaderRenderer} used by this table when drawing the table's header
95 public TableHeaderRenderer
<V
> getTableHeaderRenderer() {
96 return tableHeaderRenderer
;
100 * Replaces the {@code TableHeaderRenderer} used by this table when drawing the table's header
101 * @param tableHeaderRenderer New {@code TableHeaderRenderer} to use
104 public synchronized Table
<V
> setTableHeaderRenderer(TableHeaderRenderer
<V
> tableHeaderRenderer
) {
105 this.tableHeaderRenderer
= tableHeaderRenderer
;
111 * Sets the number of columns this table should show. If there are more columns in the table model, a scrollbar will
112 * be used to allow the user to scroll left and right and view all columns.
113 * @param visibleColumns Number of columns to display at once
115 public synchronized void setVisibleColumns(int visibleColumns
) {
116 this.visibleColumns
= visibleColumns
;
121 * Returns the number of columns this table will show. If there are more columns in the table model, a scrollbar
122 * will be used to allow the user to scroll left and right and view all columns.
123 * @return Number of visible columns for this table
125 public int getVisibleColumns() {
126 return visibleColumns
;
130 * Sets the number of rows this table will show. If there are more rows in the table model, a scrollbar will be used
131 * to allow the user to scroll up and down and view all rows.
132 * @param visibleRows Number of rows to display at once
134 public synchronized void setVisibleRows(int visibleRows
) {
135 this.visibleRows
= visibleRows
;
140 * Returns the number of rows this table will show. If there are more rows in the table model, a scrollbar will be
141 * used to allow the user to scroll up and down and view all rows.
142 * @return Number of rows to display at once
144 public int getVisibleRows() {
149 * Returns the index of the row that is currently the first row visible. This is always 0 unless scrolling has been
150 * enabled and either the user or the software (through {@code setViewTopRow(..)}) has scrolled down.
151 * @return Index of the row that is currently the first row visible
153 public int getViewTopRow() {
158 * Sets the view row offset for the first row to display in the table. Calling this with 0 will make the first row
159 * in the model be the first visible row in the table.
161 * @param viewTopRow Index of the row that is currently the first row visible
164 public synchronized Table
<V
> setViewTopRow(int viewTopRow
) {
165 this.viewTopRow
= viewTopRow
;
170 * Returns the index of the column that is currently the first column visible. This is always 0 unless scrolling has
171 * been enabled and either the user or the software (through {@code setViewLeftColumn(..)}) has scrolled to the
173 * @return Index of the column that is currently the first column visible
175 public int getViewLeftColumn() {
176 return viewLeftColumn
;
180 * Sets the view column offset for the first column to display in the table. Calling this with 0 will make the first
181 * column in the model be the first visible column in the table.
183 * @param viewLeftColumn Index of the column that is currently the first column visible
186 public synchronized Table
<V
> setViewLeftColumn(int viewLeftColumn
) {
187 this.viewLeftColumn
= viewLeftColumn
;
192 * Returns the currently selection column index, if in cell-selection mode. Otherwise it returns -1.
193 * @return In cell-selection mode returns the index of the selected column, otherwise -1
195 public int getSelectedColumn() {
196 return selectedColumn
;
200 * If in cell selection mode, updates which column is selected and ensures the selected column is visible in the
201 * view. If not in cell selection mode, does nothing.
202 * @param selectedColumn Index of the column that should be selected
205 public synchronized Table
<V
> setSelectedColumn(int selectedColumn
) {
207 this.selectedColumn
= selectedColumn
;
208 ensureSelectedItemIsVisible();
214 * Returns the index of the currently selected row
215 * @return Index of the currently selected row
217 public int getSelectedRow() {
222 * Sets the index of the selected row and ensures the selected row is visible in the view
223 * @param selectedRow Index of the row to select
226 public synchronized Table
<V
> setSelectedRow(int selectedRow
) {
227 this.selectedRow
= selectedRow
;
228 ensureSelectedItemIsVisible();
233 * If {@code true}, the user will be able to select and navigate individual cells, otherwise the user can only
235 * @param cellSelection {@code true} if cell selection should be enabled, {@code false} for row selection
238 public synchronized Table
<V
> setCellSelection(boolean cellSelection
) {
239 this.cellSelection
= cellSelection
;
240 if(cellSelection
&& selectedColumn
== -1) {
243 else if(!cellSelection
) {
250 * Returns {@code true} if this table is in cell-selection mode, otherwise {@code false}
251 * @return {@code true} if this table is in cell-selection mode, otherwise {@code false}
253 public boolean isCellSelection() {
254 return cellSelection
;
258 * Assigns an action to run whenever the user presses the enter key while focused on the table. If called with
259 * {@code null}, no action will be run.
260 * @param selectAction Action to perform when user presses the enter key
263 public synchronized Table
<V
> setSelectAction(Runnable selectAction
) {
264 this.selectAction
= selectAction
;
269 * Returns {@code true} if this table can be navigated away from when the selected row is at one of the extremes and
270 * the user presses the array key to continue in that direction. With {@code escapeByArrowKey} set to {@code true},
271 * this will move focus away from the table in the direction the user pressed, if {@code false} then nothing will
273 * @return {@code true} if user can switch focus away from the table using arrow keys, {@code false} otherwise
275 public boolean isEscapeByArrowKey() {
276 return escapeByArrowKey
;
280 * Sets the flag for if this table can be navigated away from when the selected row is at one of the extremes and
281 * the user presses the array key to continue in that direction. With {@code escapeByArrowKey} set to {@code true},
282 * this will move focus away from the table in the direction the user pressed, if {@code false} then nothing will
284 * @param escapeByArrowKey {@code true} if user can switch focus away from the table using arrow keys, {@code false} otherwise
287 public synchronized Table
<V
> setEscapeByArrowKey(boolean escapeByArrowKey
) {
288 this.escapeByArrowKey
= escapeByArrowKey
;
293 protected TableRenderer
<V
> createDefaultRenderer() {
294 return new DefaultTableRenderer
<V
>();
298 public TableRenderer
<V
> getRenderer() {
299 return (TableRenderer
<V
>)super.getRenderer();
303 public Result
handleKeyStroke(KeyStroke keyStroke
) {
304 switch(keyStroke
.getKeyType()) {
306 if(selectedRow
> 0) {
309 else if(escapeByArrowKey
) {
310 return Result
.MOVE_FOCUS_UP
;
314 if(selectedRow
< tableModel
.getRowCount() - 1) {
317 else if(escapeByArrowKey
) {
318 return Result
.MOVE_FOCUS_DOWN
;
322 if(cellSelection
&& selectedColumn
> 0) {
325 else if(escapeByArrowKey
) {
326 return Result
.MOVE_FOCUS_LEFT
;
330 if(cellSelection
&& selectedColumn
< tableModel
.getColumnCount() - 1) {
333 else if(escapeByArrowKey
) {
334 return Result
.MOVE_FOCUS_RIGHT
;
338 Runnable runnable
= selectAction
; //To avoid synchronizing
339 if(runnable
!= null) {
343 return Result
.MOVE_FOCUS_NEXT
;
347 return super.handleKeyStroke(keyStroke
);
349 ensureSelectedItemIsVisible();
351 return Result
.HANDLED
;
354 private void ensureSelectedItemIsVisible() {
355 if(visibleRows
> 0 && selectedRow
< viewTopRow
) {
356 viewTopRow
= selectedRow
;
358 else if(visibleRows
> 0 && selectedRow
>= viewTopRow
+ visibleRows
) {
359 viewTopRow
= Math
.max(0, selectedRow
- visibleRows
+ 1);
361 if(selectedColumn
!= -1) {
362 if(visibleColumns
> 0 && selectedColumn
< viewLeftColumn
) {
363 viewLeftColumn
= selectedColumn
;
365 else if(visibleColumns
> 0 && selectedColumn
>= viewLeftColumn
+ visibleColumns
) {
366 viewLeftColumn
= Math
.max(0, selectedColumn
- visibleColumns
+ 1);