2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.util
.ArrayList
;
32 import java
.util
.List
;
34 import jexer
.event
.TKeypressEvent
;
35 import jexer
.event
.TMenuEvent
;
36 import jexer
.menu
.TMenu
;
37 import static jexer
.TKeypress
.*;
40 * TTableWidget is used to display and edit regular two-dimensional tables of
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.
47 public class TTableWidget
extends TWidget
{
49 // ------------------------------------------------------------------------
50 // Constants --------------------------------------------------------------
51 // ------------------------------------------------------------------------
54 * Available borders for cells.
63 * Single bar: \u2502 (vertical) and \u2500 (horizontal).
68 * Double bar: \u2551 (vertical) and \u2550 (horizontal).
73 * Thick bar: \u258C (vertical, left half block) and \u2580
74 * (horizontal, upper block).
79 // ------------------------------------------------------------------------
80 // Variables --------------------------------------------------------------
81 // ------------------------------------------------------------------------
84 * The underlying data, organized as columns.
86 private ArrayList
<Column
> columns
= new ArrayList
<Column
>();
89 * The underlying data, organized as rows.
91 private ArrayList
<Row
> rows
= new ArrayList
<Row
>();
94 * The row in model corresponding to the top-left visible cell.
99 * The column in model corresponding to the top-left visible cell.
101 private int left
= 0;
104 * The row in model corresponding to the currently selected cell.
106 private int selectedRow
= 0;
109 * The column in model corresponding to the currently selected cell.
111 private int selectedColumn
= 0;
114 * If true, highlight the entire row of the currently-selected cell.
116 private boolean highlightRow
= false;
119 * If true, highlight the entire column of the currently-selected cell.
121 private boolean highlightColumn
= false;
124 * Column represents a column of cells.
126 public class Column
{
131 private int width
= 8;
134 * The cells of this column.
136 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
141 private String label
= "";
144 * The border for this column.
146 private Border border
= Border
.NONE
;
149 * Add an entry to this column.
151 * @param cell the cell to add
153 public void add(final Cell cell
) {
158 * Get an entry from this column.
160 * @param row the entry index to get
161 * @return the cell at row
163 public Cell
get(final int row
) {
164 return cells
.get(row
);
169 * Row represents a row of cells.
176 private int height
= 1;
179 * The cells of this row.
181 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
186 private String label
= "";
189 * The border for this row.
191 private Border border
= Border
.NONE
;
194 * Add an entry to this column.
196 * @param cell the cell to add
198 public void add(final Cell cell
) {
203 * Get an entry from this row.
205 * @param column the entry index to get
206 * @return the cell at column
208 public Cell
get(final int column
) {
209 return cells
.get(column
);
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
219 public class Cell
extends TWidget
{
221 // --------------------------------------------------------------------
222 // Variables ----------------------------------------------------------
223 // --------------------------------------------------------------------
226 * The field containing the cell's data.
228 private TField field
;
231 * The column of this cell.
236 * The row of this cell.
241 * If true, the cell is being edited.
243 private boolean isEditing
= false;
246 * Text of field before editing.
248 private String fieldText
;
250 // --------------------------------------------------------------------
251 // Constructors -------------------------------------------------------
252 // --------------------------------------------------------------------
255 * Public constructor.
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
265 public Cell(final TTableWidget parent
, final int x
, final int y
,
266 final int width
, final int height
, final int column
,
269 super(parent
, x
, y
, width
, height
);
270 this.column
= column
;
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(' ');
280 // --------------------------------------------------------------------
281 // Event handlers -----------------------------------------------------
282 // --------------------------------------------------------------------
287 * @param keypress keystroke event
290 public void onKeypress(final TKeypressEvent keypress
) {
291 // System.err.println("Cell onKeypress: " + keypress);
294 if (keypress
.equals(kbEsc
)) {
295 // ESC cancels the edit.
296 field
.setText(fieldText
);
298 field
.setEnabled(false);
301 if (keypress
.equals(kbEnter
)) {
302 // Enter ends editing.
303 fieldText
= field
.getText();
305 field
.setEnabled(false);
308 // Pass down to field.
309 super.onKeypress(keypress
);
312 if (keypress
.equals(kbEnter
) || keypress
.equals(kbF2
)) {
313 // Enter or F2 starts editing.
314 fieldText
= field
.getText();
316 field
.setEnabled(true);
322 // --------------------------------------------------------------------
323 // TWidget ------------------------------------------------------------
324 // --------------------------------------------------------------------
331 TTableWidget table
= (TTableWidget
) getParent();
333 field
.setActiveColorKey("ttable.active");
334 field
.setInactiveColorKey("ttable.inactive");
336 if (isAbsoluteActive()) {
337 if (table
.selectedColumn
== column
) {
338 if ((table
.selectedRow
== row
)
339 || (table
.highlightColumn
== true)
341 field
.setActiveColorKey("ttable.active");
342 field
.setInactiveColorKey("ttable.active");
344 } else if (table
.selectedRow
== row
) {
345 if ((table
.selectedColumn
== column
)
346 || (table
.highlightRow
== true)
348 field
.setActiveColorKey("ttable.active");
349 field
.setInactiveColorKey("ttable.active");
357 // --------------------------------------------------------------------
358 // TTable.Cell --------------------------------------------------------
359 // --------------------------------------------------------------------
366 public final String
getText() {
367 return field
.getText();
373 * @param text the new field text
375 public void setText(final String text
) {
381 // ------------------------------------------------------------------------
382 // Constructors -----------------------------------------------------------
383 // ------------------------------------------------------------------------
386 * Public constructor.
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
394 public TTableWidget(final TWidget parent
, final int x
, final int y
,
395 final int width
, final int height
) {
397 super(parent
, x
, y
, width
, height
);
399 // Initialize the starting row and column.
401 columns
.add(new Column());
403 // Place a grid of cells that fit in this space.
405 for (int i
= 0; i
< height
; i
+= rows
.get(0).height
) {
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
);
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());
419 if (i
+ rows
.get(0).height
< height
) {
424 activate(columns
.get(selectedColumn
).get(selectedRow
));
427 // ------------------------------------------------------------------------
428 // Event handlers ---------------------------------------------------------
429 // ------------------------------------------------------------------------
434 * @param keypress keystroke event
437 public void onKeypress(final TKeypressEvent keypress
) {
438 if (keypress
.equals(kbTab
)
439 || keypress
.equals(kbShiftTab
)
441 // Squash tab and back-tab. They don't make sense in the TTable
446 if (getSelectedCell().isEditing
) {
447 super.onKeypress(keypress
);
451 if (keypress
.equals(kbLeft
)) {
452 if (selectedColumn
> 0) {
455 activate(columns
.get(selectedColumn
).get(selectedRow
));
456 } else if (keypress
.equals(kbRight
)) {
457 if (selectedColumn
< columns
.size() - 1) {
460 activate(columns
.get(selectedColumn
).get(selectedRow
));
461 } else if (keypress
.equals(kbUp
)) {
462 if (selectedRow
> 0) {
465 activate(columns
.get(selectedColumn
).get(selectedRow
));
466 } else if (keypress
.equals(kbDown
)) {
467 if (selectedRow
< rows
.size() - 1) {
470 activate(columns
.get(selectedColumn
).get(selectedRow
));
471 } else if (keypress
.equals(kbHome
)) {
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
)) {
479 } else if (keypress
.equals(kbPgDn
)) {
481 } else if (keypress
.equals(kbCtrlHome
)) {
483 } else if (keypress
.equals(kbCtrlEnd
)) {
487 super.onKeypress(keypress
);
492 * Handle posted menu events.
494 * @param menu menu event
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
:
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);
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);
530 case TMenu
.MID_TABLE_FILE_SAVE_CSV
:
531 case TMenu
.MID_TABLE_FILE_SAVE_TEXT
:
538 // ------------------------------------------------------------------------
539 // TWidget ----------------------------------------------------------------
540 // ------------------------------------------------------------------------
542 // ------------------------------------------------------------------------
543 // TTable -----------------------------------------------------------------
544 // ------------------------------------------------------------------------
547 * Get the currently-selected cell.
549 * @return the selected cell
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
));
559 return (columns
.get(selectedColumn
).get(selectedRow
));
563 * Get the currently-selected column.
565 * @return the selected column
567 public Column
getSelectedColumn() {
568 assert (selectedColumn
>= 0);
569 assert (columns
.size() > selectedColumn
);
570 assert (columns
.get(selectedColumn
) != null);
571 return columns
.get(selectedColumn
);
575 * Get the currently-selected row.
577 * @return the selected row
579 public Row
getSelectedRow() {
580 assert (selectedRow
>= 0);
581 assert (rows
.size() > selectedRow
);
582 assert (rows
.get(selectedRow
) != null);
583 return rows
.get(selectedRow
);