stubs for TTableWidget
[fanfix.git] / src / jexer / TTableWidget.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import jexer.event.TKeypressEvent;
35 import jexer.event.TMenuEvent;
36 import jexer.menu.TMenu;
37 import static jexer.TKeypress.*;
38
39 /**
40 * TTableWidget is used to display and edit regular two-dimensional tables of
41 * cells.
42 *
43 * This class was inspired by a TTable implementation originally developed by
44 * David "Niki" ROULET [niki@nikiroo.be], made available under MIT at
45 * https://github.com/nikiroo/jexer/tree/ttable_pull.
46 */
47 public class TTableWidget extends TWidget {
48
49 // ------------------------------------------------------------------------
50 // Constants --------------------------------------------------------------
51 // ------------------------------------------------------------------------
52
53 /**
54 * Available borders for cells.
55 */
56 public enum Border {
57 /**
58 * No border.
59 */
60 NONE,
61
62 /**
63 * Single bar: \u2502 (vertical) and \u2500 (horizontal).
64 */
65 SINGLE,
66
67 /**
68 * Double bar: \u2551 (vertical) and \u2550 (horizontal).
69 */
70 DOUBLE,
71
72 /**
73 * Thick bar: \u258C (vertical, left half block) and \u2580
74 * (horizontal, upper block).
75 */
76 THICK,
77 }
78
79 // ------------------------------------------------------------------------
80 // Variables --------------------------------------------------------------
81 // ------------------------------------------------------------------------
82
83 /**
84 * The underlying data, organized as columns.
85 */
86 private ArrayList<Column> columns = new ArrayList<Column>();
87
88 /**
89 * The underlying data, organized as rows.
90 */
91 private ArrayList<Row> rows = new ArrayList<Row>();
92
93 /**
94 * The row in model corresponding to the top-left visible cell.
95 */
96 private int top = 0;
97
98 /**
99 * The column in model corresponding to the top-left visible cell.
100 */
101 private int left = 0;
102
103 /**
104 * The row in model corresponding to the currently selected cell.
105 */
106 private int selectedRow = 0;
107
108 /**
109 * The column in model corresponding to the currently selected cell.
110 */
111 private int selectedColumn = 0;
112
113 /**
114 * If true, highlight the entire row of the currently-selected cell.
115 */
116 private boolean highlightRow = false;
117
118 /**
119 * If true, highlight the entire column of the currently-selected cell.
120 */
121 private boolean highlightColumn = false;
122
123 /**
124 * Column represents a column of cells.
125 */
126 public class Column {
127
128 /**
129 * Width of column.
130 */
131 private int width = 8;
132
133 /**
134 * The cells of this column.
135 */
136 private ArrayList<Cell> cells = new ArrayList<Cell>();
137
138 /**
139 * Column label.
140 */
141 private String label = "";
142
143 /**
144 * The border for this column.
145 */
146 private Border border = Border.NONE;
147
148 /**
149 * Add an entry to this column.
150 *
151 * @param cell the cell to add
152 */
153 public void add(final Cell cell) {
154 cells.add(cell);
155 }
156
157 /**
158 * Get an entry from this column.
159 *
160 * @param row the entry index to get
161 * @return the cell at row
162 */
163 public Cell get(final int row) {
164 return cells.get(row);
165 }
166 }
167
168 /**
169 * Row represents a row of cells.
170 */
171 public class Row {
172
173 /**
174 * Height of row.
175 */
176 private int height = 1;
177
178 /**
179 * The cells of this row.
180 */
181 private ArrayList<Cell> cells = new ArrayList<Cell>();
182
183 /**
184 * Row label.
185 */
186 private String label = "";
187
188 /**
189 * The border for this row.
190 */
191 private Border border = Border.NONE;
192
193 /**
194 * Add an entry to this column.
195 *
196 * @param cell the cell to add
197 */
198 public void add(final Cell cell) {
199 cells.add(cell);
200 }
201
202 /**
203 * Get an entry from this row.
204 *
205 * @param column the entry index to get
206 * @return the cell at column
207 */
208 public Cell get(final int column) {
209 return cells.get(column);
210 }
211
212 }
213
214 /**
215 * Cell represents an editable cell in the table. Normally, navigation
216 * to a cell only highlights it; pressing Enter or F2 will switch to
217 * editing mode.
218 */
219 public class Cell extends TWidget {
220
221 // --------------------------------------------------------------------
222 // Variables ----------------------------------------------------------
223 // --------------------------------------------------------------------
224
225 /**
226 * The field containing the cell's data.
227 */
228 private TField field;
229
230 /**
231 * The column of this cell.
232 */
233 private int column;
234
235 /**
236 * The row of this cell.
237 */
238 private int row;
239
240 /**
241 * If true, the cell is being edited.
242 */
243 private boolean isEditing = false;
244
245 /**
246 * Text of field before editing.
247 */
248 private String fieldText;
249
250 // --------------------------------------------------------------------
251 // Constructors -------------------------------------------------------
252 // --------------------------------------------------------------------
253
254 /**
255 * Public constructor.
256 *
257 * @param parent parent widget
258 * @param x column relative to parent
259 * @param y row relative to parent
260 * @param width width of widget
261 * @param height height of widget
262 * @param column column index of this cell
263 * @param row row index of this cell
264 */
265 public Cell(final TTableWidget parent, final int x, final int y,
266 final int width, final int height, final int column,
267 final int row) {
268
269 super(parent, x, y, width, height);
270 this.column = column;
271 this.row = row;
272
273 field = addField(0, 0, width - 1, false);
274 field.setEnabled(false);
275 field.setActiveColorKey("ttable.active");
276 field.setInactiveColorKey("ttable.inactive");
277 field.setBackgroundChar(' ');
278 }
279
280 // --------------------------------------------------------------------
281 // Event handlers -----------------------------------------------------
282 // --------------------------------------------------------------------
283
284 /**
285 * Handle keystrokes.
286 *
287 * @param keypress keystroke event
288 */
289 @Override
290 public void onKeypress(final TKeypressEvent keypress) {
291 // System.err.println("Cell onKeypress: " + keypress);
292
293 if (isEditing) {
294 if (keypress.equals(kbEsc)) {
295 // ESC cancels the edit.
296 field.setText(fieldText);
297 isEditing = false;
298 field.setEnabled(false);
299 return;
300 }
301 if (keypress.equals(kbEnter)) {
302 // Enter ends editing.
303 fieldText = field.getText();
304 isEditing = false;
305 field.setEnabled(false);
306 return;
307 }
308 // Pass down to field.
309 super.onKeypress(keypress);
310 }
311
312 if (keypress.equals(kbEnter) || keypress.equals(kbF2)) {
313 // Enter or F2 starts editing.
314 fieldText = field.getText();
315 isEditing = true;
316 field.setEnabled(true);
317 activate(field);
318 return;
319 }
320 }
321
322 // --------------------------------------------------------------------
323 // TWidget ------------------------------------------------------------
324 // --------------------------------------------------------------------
325
326 /**
327 * Draw this cell.
328 */
329 @Override
330 public void draw() {
331 TTableWidget table = (TTableWidget) getParent();
332
333 field.setActiveColorKey("ttable.active");
334 field.setInactiveColorKey("ttable.inactive");
335
336 if (isAbsoluteActive()) {
337 if (table.selectedColumn == column) {
338 if ((table.selectedRow == row)
339 || (table.highlightColumn == true)
340 ) {
341 field.setActiveColorKey("ttable.active");
342 field.setInactiveColorKey("ttable.active");
343 }
344 } else if (table.selectedRow == row) {
345 if ((table.selectedColumn == column)
346 || (table.highlightRow == true)
347 ) {
348 field.setActiveColorKey("ttable.active");
349 field.setInactiveColorKey("ttable.active");
350 }
351 }
352 }
353
354 super.draw();
355 }
356
357 // --------------------------------------------------------------------
358 // TTable.Cell --------------------------------------------------------
359 // --------------------------------------------------------------------
360
361 /**
362 * Get field text.
363 *
364 * @return field text
365 */
366 public final String getText() {
367 return field.getText();
368 }
369
370 /**
371 * Set field text.
372 *
373 * @param text the new field text
374 */
375 public void setText(final String text) {
376 field.setText(text);
377 }
378
379 }
380
381 // ------------------------------------------------------------------------
382 // Constructors -----------------------------------------------------------
383 // ------------------------------------------------------------------------
384
385 /**
386 * Public constructor.
387 *
388 * @param parent parent widget
389 * @param x column relative to parent
390 * @param y row relative to parent
391 * @param width width of widget
392 * @param height height of widget
393 */
394 public TTableWidget(final TWidget parent, final int x, final int y,
395 final int width, final int height) {
396
397 super(parent, x, y, width, height);
398
399 // Initialize the starting row and column.
400 rows.add(new Row());
401 columns.add(new Column());
402
403 // Place a grid of cells that fit in this space.
404 int row = 0;
405 for (int i = 0; i < height; i += rows.get(0).height) {
406 int column = 0;
407 for (int j = 0; j < width; j += columns.get(0).width) {
408 Cell cell = new Cell(this, j, i, columns.get(0).width,
409 rows.get(0).height, column, row);
410
411 cell.setText("" + row + " " + column);
412 rows.get(row).add(cell);
413 columns.get(column).add(cell);
414 if ((i == 0) && (j + columns.get(0).width < width)) {
415 columns.add(new Column());
416 }
417 column++;
418 }
419 if (i + rows.get(0).height < height) {
420 rows.add(new Row());
421 }
422 row++;
423 }
424 activate(columns.get(selectedColumn).get(selectedRow));
425 }
426
427 // ------------------------------------------------------------------------
428 // Event handlers ---------------------------------------------------------
429 // ------------------------------------------------------------------------
430
431 /**
432 * Handle keystrokes.
433 *
434 * @param keypress keystroke event
435 */
436 @Override
437 public void onKeypress(final TKeypressEvent keypress) {
438 if (keypress.equals(kbTab)
439 || keypress.equals(kbShiftTab)
440 ) {
441 // Squash tab and back-tab. They don't make sense in the TTable
442 // grid context.
443 return;
444 }
445
446 if (getSelectedCell().isEditing) {
447 super.onKeypress(keypress);
448 return;
449 }
450
451 if (keypress.equals(kbLeft)) {
452 if (selectedColumn > 0) {
453 selectedColumn--;
454 }
455 activate(columns.get(selectedColumn).get(selectedRow));
456 } else if (keypress.equals(kbRight)) {
457 if (selectedColumn < columns.size() - 1) {
458 selectedColumn++;
459 }
460 activate(columns.get(selectedColumn).get(selectedRow));
461 } else if (keypress.equals(kbUp)) {
462 if (selectedRow > 0) {
463 selectedRow--;
464 }
465 activate(columns.get(selectedColumn).get(selectedRow));
466 } else if (keypress.equals(kbDown)) {
467 if (selectedRow < rows.size() - 1) {
468 selectedRow++;
469 }
470 activate(columns.get(selectedColumn).get(selectedRow));
471 } else if (keypress.equals(kbHome)) {
472 selectedColumn = 0;
473 activate(columns.get(selectedColumn).get(selectedRow));
474 } else if (keypress.equals(kbEnd)) {
475 selectedColumn = columns.size() - 1;
476 activate(columns.get(selectedColumn).get(selectedRow));
477 } else if (keypress.equals(kbPgUp)) {
478 // TODO
479 } else if (keypress.equals(kbPgDn)) {
480 // TODO
481 } else if (keypress.equals(kbCtrlHome)) {
482 // TODO
483 } else if (keypress.equals(kbCtrlEnd)) {
484 // TODO
485 } else {
486 // Pass to the Cell.
487 super.onKeypress(keypress);
488 }
489 }
490
491 /**
492 * Handle posted menu events.
493 *
494 * @param menu menu event
495 */
496 @Override
497 public void onMenu(final TMenuEvent menu) {
498 switch (menu.getId()) {
499 case TMenu.MID_TABLE_BORDER_NONE:
500 case TMenu.MID_TABLE_BORDER_ALL:
501 case TMenu.MID_TABLE_BORDER_RIGHT:
502 case TMenu.MID_TABLE_BORDER_LEFT:
503 case TMenu.MID_TABLE_BORDER_TOP:
504 case TMenu.MID_TABLE_BORDER_BOTTOM:
505 case TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM:
506 case TMenu.MID_TABLE_BORDER_THICK_BOTTOM:
507 case TMenu.MID_TABLE_DELETE_LEFT:
508 case TMenu.MID_TABLE_DELETE_UP:
509 case TMenu.MID_TABLE_DELETE_ROW:
510 case TMenu.MID_TABLE_DELETE_COLUMN:
511 case TMenu.MID_TABLE_INSERT_LEFT:
512 case TMenu.MID_TABLE_INSERT_RIGHT:
513 case TMenu.MID_TABLE_INSERT_ABOVE:
514 case TMenu.MID_TABLE_INSERT_BELOW:
515 break;
516 case TMenu.MID_TABLE_COLUMN_NARROW:
517 columns.get(selectedColumn).width--;
518 for (Cell cell: getSelectedColumn().cells) {
519 cell.setWidth(columns.get(selectedColumn).width);
520 cell.field.setWidth(columns.get(selectedColumn).width - 1);
521 }
522 break;
523 case TMenu.MID_TABLE_COLUMN_WIDEN:
524 columns.get(selectedColumn).width++;
525 for (Cell cell: getSelectedColumn().cells) {
526 cell.setWidth(columns.get(selectedColumn).width);
527 cell.field.setWidth(columns.get(selectedColumn).width - 1);
528 }
529 break;
530 case TMenu.MID_TABLE_FILE_SAVE_CSV:
531 case TMenu.MID_TABLE_FILE_SAVE_TEXT:
532 break;
533 default:
534 super.onMenu(menu);
535 }
536 }
537
538 // ------------------------------------------------------------------------
539 // TWidget ----------------------------------------------------------------
540 // ------------------------------------------------------------------------
541
542 // ------------------------------------------------------------------------
543 // TTable -----------------------------------------------------------------
544 // ------------------------------------------------------------------------
545
546 /**
547 * Get the currently-selected cell.
548 *
549 * @return the selected cell
550 */
551 public Cell getSelectedCell() {
552 assert (rows.get(selectedRow) != null);
553 assert (rows.get(selectedRow).get(selectedColumn) != null);
554 assert (columns.get(selectedColumn) != null);
555 assert (columns.get(selectedColumn).get(selectedRow) != null);
556 assert (rows.get(selectedRow).get(selectedColumn) ==
557 columns.get(selectedColumn).get(selectedRow));
558
559 return (columns.get(selectedColumn).get(selectedRow));
560 }
561
562 /**
563 * Get the currently-selected column.
564 *
565 * @return the selected column
566 */
567 public Column getSelectedColumn() {
568 assert (selectedColumn >= 0);
569 assert (columns.size() > selectedColumn);
570 assert (columns.get(selectedColumn) != null);
571 return columns.get(selectedColumn);
572 }
573
574 /**
575 * Get the currently-selected row.
576 *
577 * @return the selected row
578 */
579 public Row getSelectedRow() {
580 assert (selectedRow >= 0);
581 assert (rows.size() > selectedRow);
582 assert (rows.get(selectedRow) != null);
583 return rows.get(selectedRow);
584 }
585
586 }