Change build scripts
[jvcard.git] / src / com / googlecode / lanterna / gui2 / table / Table.java
CommitLineData
a3b510ab
NR
1package com.googlecode.lanterna.gui2.table;
2
3import com.googlecode.lanterna.gui2.*;
4import com.googlecode.lanterna.input.KeyStroke;
5
6/**
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
10 * on the keyboard.
11 * @param <V> Type of data to store in the table cells, presented through {@code toString()}
12 * @author Martin
13 */
14public 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;
27
28 /**
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
31 */
32 public Table(String... columnLabels) {
33 if(columnLabels.length == 0) {
34 throw new IllegalArgumentException("Table needs at least one column");
35 }
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;
41 this.visibleRows = 0;
42 this.viewTopRow = 0;
43 this.viewLeftColumn = 0;
44 this.cellSelection = false;
45 this.selectedRow = 0;
46 this.selectedColumn = -1;
47 this.escapeByArrowKey = true;
48 }
49
50 /**
51 * Returns the underlying table model
52 * @return Underlying table model
53 */
54 public TableModel<V> getTableModel() {
55 return tableModel;
56 }
57
58 /**
59 * Updates the table with a new table model, effectively replacing the content of the table completely
60 * @param tableModel New table model
61 * @return Itself
62 */
63 public synchronized Table<V> setTableModel(TableModel<V> tableModel) {
64 if(tableModel == null) {
65 throw new IllegalArgumentException("Cannot assign a null TableModel");
66 }
67 this.tableModel = tableModel;
68 invalidate();
69 return this;
70 }
71
72 /**
73 * Returns the {@code TableCellRenderer} used by this table when drawing cells
74 * @return {@code TableCellRenderer} used by this table when drawing cells
75 */
76 public TableCellRenderer<V> getTableCellRenderer() {
77 return tableCellRenderer;
78 }
79
80 /**
81 * Replaces the {@code TableCellRenderer} used by this table when drawing cells
82 * @param tableCellRenderer New {@code TableCellRenderer} to use
83 * @return Itself
84 */
85 public synchronized Table<V> setTableCellRenderer(TableCellRenderer<V> tableCellRenderer) {
86 this.tableCellRenderer = tableCellRenderer;
87 invalidate();
88 return this;
89 }
90
91 /**
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
94 */
95 public TableHeaderRenderer<V> getTableHeaderRenderer() {
96 return tableHeaderRenderer;
97 }
98
99 /**
100 * Replaces the {@code TableHeaderRenderer} used by this table when drawing the table's header
101 * @param tableHeaderRenderer New {@code TableHeaderRenderer} to use
102 * @return Itself
103 */
104 public synchronized Table<V> setTableHeaderRenderer(TableHeaderRenderer<V> tableHeaderRenderer) {
105 this.tableHeaderRenderer = tableHeaderRenderer;
106 invalidate();
107 return this;
108 }
109
110 /**
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
114 */
115 public synchronized void setVisibleColumns(int visibleColumns) {
116 this.visibleColumns = visibleColumns;
117 invalidate();
118 }
119
120 /**
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
124 */
125 public int getVisibleColumns() {
126 return visibleColumns;
127 }
128
129 /**
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
133 */
134 public synchronized void setVisibleRows(int visibleRows) {
135 this.visibleRows = visibleRows;
136 invalidate();
137 }
138
139 /**
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
143 */
144 public int getVisibleRows() {
145 return visibleRows;
146 }
147
148 /**
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
152 */
153 public int getViewTopRow() {
154 return viewTopRow;
155 }
156
157 /**
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.
160 *
161 * @param viewTopRow Index of the row that is currently the first row visible
162 * @return Itself
163 */
164 public synchronized Table<V> setViewTopRow(int viewTopRow) {
165 this.viewTopRow = viewTopRow;
166 return this;
167 }
168
169 /**
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
172 * right.
173 * @return Index of the column that is currently the first column visible
174 */
175 public int getViewLeftColumn() {
176 return viewLeftColumn;
177 }
178
179 /**
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.
182 *
183 * @param viewLeftColumn Index of the column that is currently the first column visible
184 * @return Itself
185 */
186 public synchronized Table<V> setViewLeftColumn(int viewLeftColumn) {
187 this.viewLeftColumn = viewLeftColumn;
188 return this;
189 }
190
191 /**
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
194 */
195 public int getSelectedColumn() {
196 return selectedColumn;
197 }
198
199 /**
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
203 * @return Itself
204 */
205 public synchronized Table<V> setSelectedColumn(int selectedColumn) {
206 if(cellSelection) {
207 this.selectedColumn = selectedColumn;
208 ensureSelectedItemIsVisible();
209 }
210 return this;
211 }
212
213 /**
214 * Returns the index of the currently selected row
215 * @return Index of the currently selected row
216 */
217 public int getSelectedRow() {
218 return selectedRow;
219 }
220
221 /**
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
224 * @return Itself
225 */
226 public synchronized Table<V> setSelectedRow(int selectedRow) {
227 this.selectedRow = selectedRow;
228 ensureSelectedItemIsVisible();
229 return this;
230 }
231
232 /**
233 * If {@code true}, the user will be able to select and navigate individual cells, otherwise the user can only
234 * select full rows.
235 * @param cellSelection {@code true} if cell selection should be enabled, {@code false} for row selection
236 * @return Itself
237 */
238 public synchronized Table<V> setCellSelection(boolean cellSelection) {
239 this.cellSelection = cellSelection;
240 if(cellSelection && selectedColumn == -1) {
241 selectedColumn = 0;
242 }
243 else if(!cellSelection) {
244 selectedColumn = -1;
245 }
246 return this;
247 }
248
249 /**
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}
252 */
253 public boolean isCellSelection() {
254 return cellSelection;
255 }
256
257 /**
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
261 * @return Itself
262 */
263 public synchronized Table<V> setSelectAction(Runnable selectAction) {
264 this.selectAction = selectAction;
265 return this;
266 }
267
268 /**
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
272 * happen.
273 * @return {@code true} if user can switch focus away from the table using arrow keys, {@code false} otherwise
274 */
275 public boolean isEscapeByArrowKey() {
276 return escapeByArrowKey;
277 }
278
279 /**
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
283 * happen.
284 * @param escapeByArrowKey {@code true} if user can switch focus away from the table using arrow keys, {@code false} otherwise
285 * @return Itself
286 */
287 public synchronized Table<V> setEscapeByArrowKey(boolean escapeByArrowKey) {
288 this.escapeByArrowKey = escapeByArrowKey;
289 return this;
290 }
291
292 @Override
293 protected TableRenderer<V> createDefaultRenderer() {
294 return new DefaultTableRenderer<V>();
295 }
296
297 @Override
298 public TableRenderer<V> getRenderer() {
299 return (TableRenderer<V>)super.getRenderer();
300 }
301
302 @Override
303 public Result handleKeyStroke(KeyStroke keyStroke) {
304 switch(keyStroke.getKeyType()) {
305 case ArrowUp:
306 if(selectedRow > 0) {
307 selectedRow--;
308 }
309 else if(escapeByArrowKey) {
310 return Result.MOVE_FOCUS_UP;
311 }
312 break;
313 case ArrowDown:
314 if(selectedRow < tableModel.getRowCount() - 1) {
315 selectedRow++;
316 }
317 else if(escapeByArrowKey) {
318 return Result.MOVE_FOCUS_DOWN;
319 }
320 break;
321 case ArrowLeft:
322 if(cellSelection && selectedColumn > 0) {
323 selectedColumn--;
324 }
325 else if(escapeByArrowKey) {
326 return Result.MOVE_FOCUS_LEFT;
327 }
328 break;
329 case ArrowRight:
330 if(cellSelection && selectedColumn < tableModel.getColumnCount() - 1) {
331 selectedColumn++;
332 }
333 else if(escapeByArrowKey) {
334 return Result.MOVE_FOCUS_RIGHT;
335 }
336 break;
337 case Enter:
338 Runnable runnable = selectAction; //To avoid synchronizing
339 if(runnable != null) {
340 runnable.run();
341 }
342 else {
343 return Result.MOVE_FOCUS_NEXT;
344 }
345 break;
346 default:
347 return super.handleKeyStroke(keyStroke);
348 }
349 ensureSelectedItemIsVisible();
350 invalidate();
351 return Result.HANDLED;
352 }
353
354 private void ensureSelectedItemIsVisible() {
355 if(visibleRows > 0 && selectedRow < viewTopRow) {
356 viewTopRow = selectedRow;
357 }
358 else if(visibleRows > 0 && selectedRow >= viewTopRow + visibleRows) {
359 viewTopRow = Math.max(0, selectedRow - visibleRows + 1);
360 }
361 if(selectedColumn != -1) {
362 if(visibleColumns > 0 && selectedColumn < viewLeftColumn) {
363 viewLeftColumn = selectedColumn;
364 }
365 else if(visibleColumns > 0 && selectedColumn >= viewLeftColumn + visibleColumns) {
366 viewLeftColumn = Math.max(0, selectedColumn - visibleColumns + 1);
367 }
368 }
369 }
370}