ttable navigation correct
[fanfix.git] / src / jexer / TTableWidget.java
CommitLineData
1dac6b8d
KL
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 */
29package jexer;
30
759cb83e 31import java.io.IOException;
1dac6b8d
KL
32import java.util.ArrayList;
33import java.util.List;
34
68ee64d0 35import jexer.bits.CellAttributes;
1dac6b8d
KL
36import jexer.event.TKeypressEvent;
37import jexer.event.TMenuEvent;
759cb83e 38import jexer.event.TMouseEvent;
2e1384cc 39import jexer.event.TResizeEvent;
1dac6b8d
KL
40import jexer.menu.TMenu;
41import static jexer.TKeypress.*;
42
43/**
44 * TTableWidget is used to display and edit regular two-dimensional tables of
45 * cells.
46 *
47 * This class was inspired by a TTable implementation originally developed by
48 * David "Niki" ROULET [niki@nikiroo.be], made available under MIT at
49 * https://github.com/nikiroo/jexer/tree/ttable_pull.
50 */
51public class TTableWidget extends TWidget {
52
53 // ------------------------------------------------------------------------
54 // Constants --------------------------------------------------------------
55 // ------------------------------------------------------------------------
56
57 /**
58 * Available borders for cells.
59 */
60 public enum Border {
61 /**
62 * No border.
63 */
64 NONE,
65
66 /**
67 * Single bar: \u2502 (vertical) and \u2500 (horizontal).
68 */
69 SINGLE,
70
71 /**
72 * Double bar: \u2551 (vertical) and \u2550 (horizontal).
73 */
74 DOUBLE,
75
76 /**
77 * Thick bar: \u258C (vertical, left half block) and \u2580
78 * (horizontal, upper block).
79 */
80 THICK,
81 }
82
73162e7f
KL
83 /**
84 * Row label width.
85 */
86 private static final int ROW_LABEL_WIDTH = 8;
87
88 /**
89 * Column label height.
90 */
91 private static final int COLUMN_LABEL_HEIGHT = 1;
92
11cedc9a
KL
93 /**
94 * Column default width.
95 */
96 private static final int COLUMN_DEFAULT_WIDTH = 8;
97
73162e7f
KL
98 /**
99 * Extra rows to add.
100 */
101 private static final int EXTRA_ROWS = 10;
102
103 /**
104 * Extra columns to add.
105 */
106 private static final int EXTRA_COLUMNS = 10 * (8 + 1);
107
1dac6b8d
KL
108 // ------------------------------------------------------------------------
109 // Variables --------------------------------------------------------------
110 // ------------------------------------------------------------------------
111
112 /**
113 * The underlying data, organized as columns.
114 */
115 private ArrayList<Column> columns = new ArrayList<Column>();
116
117 /**
118 * The underlying data, organized as rows.
119 */
120 private ArrayList<Row> rows = new ArrayList<Row>();
121
122 /**
123 * The row in model corresponding to the top-left visible cell.
124 */
125 private int top = 0;
126
127 /**
128 * The column in model corresponding to the top-left visible cell.
129 */
130 private int left = 0;
131
132 /**
133 * The row in model corresponding to the currently selected cell.
134 */
135 private int selectedRow = 0;
136
137 /**
138 * The column in model corresponding to the currently selected cell.
139 */
140 private int selectedColumn = 0;
141
142 /**
143 * If true, highlight the entire row of the currently-selected cell.
144 */
73162e7f 145 private boolean highlightRow = false;
1dac6b8d
KL
146
147 /**
148 * If true, highlight the entire column of the currently-selected cell.
149 */
73162e7f 150 private boolean highlightColumn = false;
9c172016
KL
151
152 /**
153 * If true, show the row labels as the first column.
154 */
155 private boolean showRowLabels = true;
1dac6b8d 156
68ee64d0
KL
157 /**
158 * If true, show the column labels as the first row.
159 */
160 private boolean showColumnLabels = true;
161
73162e7f
KL
162 /**
163 * The top border for the first row.
164 */
165 private Border topBorder = Border.NONE;
166
167 /**
168 * The left border for the first column.
169 */
170 private Border leftBorder = Border.NONE;
171
1dac6b8d
KL
172 /**
173 * Column represents a column of cells.
174 */
175 public class Column {
176
11cedc9a
KL
177 /**
178 * X position of this column.
179 */
180 private int x = 0;
181
1dac6b8d
KL
182 /**
183 * Width of column.
184 */
11cedc9a 185 private int width = COLUMN_DEFAULT_WIDTH;
1dac6b8d
KL
186
187 /**
188 * The cells of this column.
189 */
190 private ArrayList<Cell> cells = new ArrayList<Cell>();
191
192 /**
193 * Column label.
194 */
68ee64d0 195 private String label = "";
1dac6b8d
KL
196
197 /**
73162e7f 198 * The right border for this column.
1dac6b8d 199 */
73162e7f 200 private Border rightBorder = Border.NONE;
1dac6b8d 201
9c172016
KL
202 /**
203 * Constructor sets label to lettered column.
204 *
205 * @param col column number to use for this column. Column 0 will be
206 * "A", column 1 will be "B", column 26 will be "AA", and so on.
207 */
208 Column(int col) {
209 StringBuilder sb = new StringBuilder();
210 for (;;) {
211 sb.append((char) ('A' + (col % 26)));
212 if (col < 26) {
213 break;
214 }
215 col /= 26;
216 }
217 label = sb.reverse().toString();
218 }
219
1dac6b8d
KL
220 /**
221 * Add an entry to this column.
222 *
223 * @param cell the cell to add
224 */
225 public void add(final Cell cell) {
226 cells.add(cell);
227 }
228
229 /**
230 * Get an entry from this column.
231 *
232 * @param row the entry index to get
233 * @return the cell at row
234 */
235 public Cell get(final int row) {
236 return cells.get(row);
237 }
11cedc9a
KL
238
239 /**
240 * Get the X position of the cells in this column.
241 *
242 * @return the position
243 */
244 public int getX() {
245 return x;
246 }
247
248 /**
249 * Set the X position of the cells in this column.
250 *
251 * @param x the position
252 */
253 public void setX(final int x) {
254 for (Cell cell: cells) {
255 cell.setX(x);
256 }
257 this.x = x;
258 }
259
1dac6b8d
KL
260 }
261
262 /**
263 * Row represents a row of cells.
264 */
265 public class Row {
266
11cedc9a
KL
267 /**
268 * Y position of this row.
269 */
270 private int y = 0;
271
1dac6b8d
KL
272 /**
273 * Height of row.
274 */
275 private int height = 1;
276
277 /**
278 * The cells of this row.
279 */
280 private ArrayList<Cell> cells = new ArrayList<Cell>();
281
282 /**
283 * Row label.
284 */
285 private String label = "";
286
287 /**
73162e7f 288 * The bottom border for this row.
1dac6b8d 289 */
73162e7f 290 private Border bottomBorder = Border.NONE;
1dac6b8d 291
9c172016
KL
292 /**
293 * Constructor sets label to numbered row.
294 *
295 * @param row row number to use for this row
296 */
297 Row(final int row) {
298 label = Integer.toString(row);
299 }
300
1dac6b8d
KL
301 /**
302 * Add an entry to this column.
303 *
304 * @param cell the cell to add
305 */
306 public void add(final Cell cell) {
307 cells.add(cell);
308 }
309
310 /**
311 * Get an entry from this row.
312 *
313 * @param column the entry index to get
314 * @return the cell at column
315 */
316 public Cell get(final int column) {
317 return cells.get(column);
318 }
11cedc9a
KL
319 /**
320 * Get the Y position of the cells in this column.
321 *
322 * @return the position
323 */
324 public int getY() {
325 return y;
326 }
327
328 /**
329 * Set the Y position of the cells in this column.
330 *
331 * @param y the position
332 */
333 public void setY(final int y) {
334 for (Cell cell: cells) {
335 cell.setY(y);
336 }
337 this.y = y;
338 }
1dac6b8d
KL
339
340 }
341
342 /**
343 * Cell represents an editable cell in the table. Normally, navigation
344 * to a cell only highlights it; pressing Enter or F2 will switch to
345 * editing mode.
346 */
347 public class Cell extends TWidget {
348
349 // --------------------------------------------------------------------
350 // Variables ----------------------------------------------------------
351 // --------------------------------------------------------------------
352
353 /**
354 * The field containing the cell's data.
355 */
356 private TField field;
357
358 /**
359 * The column of this cell.
360 */
361 private int column;
362
363 /**
364 * The row of this cell.
365 */
366 private int row;
367
368 /**
369 * If true, the cell is being edited.
370 */
371 private boolean isEditing = false;
372
373 /**
374 * Text of field before editing.
375 */
376 private String fieldText;
377
378 // --------------------------------------------------------------------
379 // Constructors -------------------------------------------------------
380 // --------------------------------------------------------------------
381
382 /**
383 * Public constructor.
384 *
385 * @param parent parent widget
386 * @param x column relative to parent
387 * @param y row relative to parent
388 * @param width width of widget
389 * @param height height of widget
390 * @param column column index of this cell
391 * @param row row index of this cell
392 */
393 public Cell(final TTableWidget parent, final int x, final int y,
394 final int width, final int height, final int column,
395 final int row) {
396
397 super(parent, x, y, width, height);
398 this.column = column;
399 this.row = row;
400
2e1384cc 401 field = addField(0, 0, width, false);
1dac6b8d 402 field.setEnabled(false);
1dac6b8d
KL
403 field.setBackgroundChar(' ');
404 }
405
406 // --------------------------------------------------------------------
407 // Event handlers -----------------------------------------------------
408 // --------------------------------------------------------------------
409
759cb83e
KL
410 /**
411 * Handle mouse double-click events.
412 *
413 * @param mouse mouse double-click event
414 */
415 @Override
416 public void onMouseDoubleClick(final TMouseEvent mouse) {
417 // Use TWidget's code to pass the event to the children.
418 super.onMouseDown(mouse);
419
420 // Double-click means to start editing.
421 fieldText = field.getText();
422 isEditing = true;
423 field.setEnabled(true);
424 activate(field);
425
426 if (isActive()) {
427 // Let the table know that I was activated.
428 ((TTableWidget) getParent()).selectedRow = row;
429 ((TTableWidget) getParent()).selectedColumn = column;
11cedc9a 430 ((TTableWidget) getParent()).alignGrid();
759cb83e
KL
431 }
432 }
433
434 /**
435 * Handle mouse press events.
436 *
437 * @param mouse mouse button press event
438 */
439 @Override
440 public void onMouseDown(final TMouseEvent mouse) {
441 // Use TWidget's code to pass the event to the children.
442 super.onMouseDown(mouse);
443
444 if (isActive()) {
445 // Let the table know that I was activated.
446 ((TTableWidget) getParent()).selectedRow = row;
447 ((TTableWidget) getParent()).selectedColumn = column;
11cedc9a 448 ((TTableWidget) getParent()).alignGrid();
759cb83e
KL
449 }
450 }
451
452 /**
453 * Handle mouse release events.
454 *
455 * @param mouse mouse button release event
456 */
457 @Override
458 public void onMouseUp(final TMouseEvent mouse) {
459 // Use TWidget's code to pass the event to the children.
460 super.onMouseDown(mouse);
461
462 if (isActive()) {
463 // Let the table know that I was activated.
464 ((TTableWidget) getParent()).selectedRow = row;
465 ((TTableWidget) getParent()).selectedColumn = column;
11cedc9a 466 ((TTableWidget) getParent()).alignGrid();
759cb83e
KL
467 }
468 }
469
1dac6b8d
KL
470 /**
471 * Handle keystrokes.
472 *
473 * @param keypress keystroke event
474 */
475 @Override
476 public void onKeypress(final TKeypressEvent keypress) {
477 // System.err.println("Cell onKeypress: " + keypress);
478
479 if (isEditing) {
480 if (keypress.equals(kbEsc)) {
481 // ESC cancels the edit.
482 field.setText(fieldText);
483 isEditing = false;
484 field.setEnabled(false);
485 return;
486 }
487 if (keypress.equals(kbEnter)) {
488 // Enter ends editing.
489 fieldText = field.getText();
490 isEditing = false;
491 field.setEnabled(false);
492 return;
493 }
494 // Pass down to field.
495 super.onKeypress(keypress);
496 }
497
498 if (keypress.equals(kbEnter) || keypress.equals(kbF2)) {
499 // Enter or F2 starts editing.
500 fieldText = field.getText();
501 isEditing = true;
502 field.setEnabled(true);
503 activate(field);
504 return;
505 }
506 }
507
508 // --------------------------------------------------------------------
509 // TWidget ------------------------------------------------------------
510 // --------------------------------------------------------------------
511
512 /**
513 * Draw this cell.
514 */
515 @Override
516 public void draw() {
517 TTableWidget table = (TTableWidget) getParent();
518
1dac6b8d 519 if (isAbsoluteActive()) {
9c172016
KL
520 if (isEditing) {
521 field.setActiveColorKey("tfield.active");
522 field.setInactiveColorKey("tfield.inactive");
523 } else {
524 field.setActiveColorKey("ttable.selected");
525 field.setInactiveColorKey("ttable.selected");
1dac6b8d 526 }
9c172016
KL
527 } else if (((table.selectedColumn == column)
528 && ((table.selectedRow == row)
529 || (table.highlightColumn == true)))
530 || ((table.selectedRow == row)
531 && ((table.selectedColumn == column)
532 || (table.highlightRow == true)))
533 ) {
534 field.setActiveColorKey("ttable.active");
535 field.setInactiveColorKey("ttable.active");
536 } else {
537 field.setActiveColorKey("ttable.active");
538 field.setInactiveColorKey("ttable.inactive");
1dac6b8d
KL
539 }
540
77961919
KL
541 assert (isVisible() == true);
542
1dac6b8d
KL
543 super.draw();
544 }
545
546 // --------------------------------------------------------------------
547 // TTable.Cell --------------------------------------------------------
548 // --------------------------------------------------------------------
549
550 /**
551 * Get field text.
552 *
553 * @return field text
554 */
555 public final String getText() {
556 return field.getText();
557 }
558
559 /**
560 * Set field text.
561 *
562 * @param text the new field text
563 */
564 public void setText(final String text) {
565 field.setText(text);
566 }
567
568 }
569
570 // ------------------------------------------------------------------------
571 // Constructors -----------------------------------------------------------
572 // ------------------------------------------------------------------------
573
574 /**
575 * Public constructor.
576 *
577 * @param parent parent widget
578 * @param x column relative to parent
579 * @param y row relative to parent
580 * @param width width of widget
581 * @param height height of widget
582 */
583 public TTableWidget(final TWidget parent, final int x, final int y,
584 final int width, final int height) {
585
586 super(parent, x, y, width, height);
587
588 // Initialize the starting row and column.
9c172016
KL
589 rows.add(new Row(0));
590 columns.add(new Column(0));
1dac6b8d
KL
591
592 // Place a grid of cells that fit in this space.
593 int row = 0;
73162e7f 594 for (int i = 0; i < height + EXTRA_ROWS; i += rows.get(0).height) {
1dac6b8d 595 int column = 0;
73162e7f
KL
596 for (int j = 0; j < width + EXTRA_COLUMNS;
597 j += columns.get(0).width) {
598
11cedc9a 599 Cell cell = new Cell(this, 0, 0, /* j, i, */ columns.get(0).width,
1dac6b8d
KL
600 rows.get(0).height, column, row);
601
11cedc9a
KL
602 // DEBUG: set a grid of cell index labels
603 // TODO: remove this
1dac6b8d
KL
604 cell.setText("" + row + " " + column);
605 rows.get(row).add(cell);
606 columns.get(column).add(cell);
73162e7f
KL
607 if ((i == 0) &&
608 (j + columns.get(0).width < width + EXTRA_COLUMNS)
609 ) {
9c172016 610 columns.add(new Column(column + 1));
1dac6b8d
KL
611 }
612 column++;
613 }
73162e7f 614 if (i + rows.get(0).height < height + EXTRA_ROWS) {
9c172016 615 rows.add(new Row(row + 1));
1dac6b8d
KL
616 }
617 row++;
618 }
11cedc9a
KL
619 for (int i = 0; i < rows.size(); i++) {
620 rows.get(i).setY(i + (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0));
621 }
622 for (int j = 0; j < columns.size(); j++) {
623 columns.get(j).setX((j * COLUMN_DEFAULT_WIDTH) +
624 (showRowLabels ? ROW_LABEL_WIDTH : 0));
625 }
1dac6b8d 626 activate(columns.get(selectedColumn).get(selectedRow));
9c172016 627
11cedc9a 628 alignGrid();
77961919
KL
629
630 // Set the menu to match the flags.
631 getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_ROW_LABELS).
632 setChecked(showRowLabels);
633 getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_COLUMN_LABELS).
634 setChecked(showColumnLabels);
635 getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_HIGHLIGHT_ROW).
636 setChecked(highlightRow);
637 getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_HIGHLIGHT_COLUMN).
638 setChecked(highlightColumn);
639
640
1dac6b8d
KL
641 }
642
643 // ------------------------------------------------------------------------
644 // Event handlers ---------------------------------------------------------
645 // ------------------------------------------------------------------------
646
759cb83e
KL
647 /**
648 * Handle mouse press events.
649 *
650 * @param mouse mouse button press event
651 */
652 @Override
653 public void onMouseDown(final TMouseEvent mouse) {
654 if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) {
655 // Treat wheel up/down as 3 up/down
656 TKeypressEvent keyEvent;
657 if (mouse.isMouseWheelUp()) {
658 keyEvent = new TKeypressEvent(kbUp);
659 } else {
660 keyEvent = new TKeypressEvent(kbDown);
661 }
662 for (int i = 0; i < 3; i++) {
663 onKeypress(keyEvent);
664 }
665 return;
666 }
667
668 // Use TWidget's code to pass the event to the children.
669 super.onMouseDown(mouse);
670 }
671
1dac6b8d
KL
672 /**
673 * Handle keystrokes.
674 *
675 * @param keypress keystroke event
676 */
677 @Override
678 public void onKeypress(final TKeypressEvent keypress) {
679 if (keypress.equals(kbTab)
680 || keypress.equals(kbShiftTab)
681 ) {
682 // Squash tab and back-tab. They don't make sense in the TTable
683 // grid context.
684 return;
685 }
9c172016
KL
686
687 // If editing, pass to that cell and do nothing else.
1dac6b8d
KL
688 if (getSelectedCell().isEditing) {
689 super.onKeypress(keypress);
690 return;
691 }
692
693 if (keypress.equals(kbLeft)) {
759cb83e 694 // Left
1dac6b8d
KL
695 if (selectedColumn > 0) {
696 selectedColumn--;
697 }
698 activate(columns.get(selectedColumn).get(selectedRow));
699 } else if (keypress.equals(kbRight)) {
759cb83e 700 // Right
1dac6b8d
KL
701 if (selectedColumn < columns.size() - 1) {
702 selectedColumn++;
703 }
704 activate(columns.get(selectedColumn).get(selectedRow));
705 } else if (keypress.equals(kbUp)) {
759cb83e 706 // Up
1dac6b8d
KL
707 if (selectedRow > 0) {
708 selectedRow--;
709 }
710 activate(columns.get(selectedColumn).get(selectedRow));
711 } else if (keypress.equals(kbDown)) {
759cb83e 712 // Down
1dac6b8d
KL
713 if (selectedRow < rows.size() - 1) {
714 selectedRow++;
715 }
716 activate(columns.get(selectedColumn).get(selectedRow));
717 } else if (keypress.equals(kbHome)) {
759cb83e 718 // Home - leftmost column
1dac6b8d
KL
719 selectedColumn = 0;
720 activate(columns.get(selectedColumn).get(selectedRow));
721 } else if (keypress.equals(kbEnd)) {
759cb83e 722 // End - rightmost column
1dac6b8d
KL
723 selectedColumn = columns.size() - 1;
724 activate(columns.get(selectedColumn).get(selectedRow));
725 } else if (keypress.equals(kbPgUp)) {
759cb83e
KL
726 // PgUp - Treat like multiple up
727 for (int i = 0; i < getHeight() - 2; i++) {
728 if (selectedRow > 0) {
729 selectedRow--;
730 }
731 }
732 activate(columns.get(selectedColumn).get(selectedRow));
1dac6b8d 733 } else if (keypress.equals(kbPgDn)) {
759cb83e
KL
734 // PgDn - Treat like multiple up
735 for (int i = 0; i < getHeight() - 2; i++) {
736 if (selectedRow < rows.size() - 1) {
737 selectedRow++;
738 }
739 }
740 activate(columns.get(selectedColumn).get(selectedRow));
1dac6b8d 741 } else if (keypress.equals(kbCtrlHome)) {
759cb83e
KL
742 // Ctrl-Home - go to top-left
743 selectedRow = 0;
744 selectedColumn = 0;
745 activate(columns.get(selectedColumn).get(selectedRow));
746 activate(columns.get(selectedColumn).get(selectedRow));
1dac6b8d 747 } else if (keypress.equals(kbCtrlEnd)) {
759cb83e
KL
748 // Ctrl-End - go to bottom-right
749 selectedRow = rows.size() - 1;
750 selectedColumn = columns.size() - 1;
751 activate(columns.get(selectedColumn).get(selectedRow));
752 activate(columns.get(selectedColumn).get(selectedRow));
1dac6b8d
KL
753 } else {
754 // Pass to the Cell.
755 super.onKeypress(keypress);
756 }
9c172016
KL
757
758 // We may have scrolled off screen. Reset positions as needed to
759 // make the newly selected cell visible.
11cedc9a 760 alignGrid();
1dac6b8d
KL
761 }
762
2e1384cc
KL
763 /**
764 * Handle widget resize events.
765 *
766 * @param event resize event
767 */
768 @Override
769 public void onResize(final TResizeEvent event) {
770 super.onResize(event);
771
11cedc9a 772 alignGrid();
2e1384cc
KL
773 }
774
1dac6b8d
KL
775 /**
776 * Handle posted menu events.
777 *
778 * @param menu menu event
779 */
780 @Override
781 public void onMenu(final TMenuEvent menu) {
782 switch (menu.getId()) {
77961919
KL
783 case TMenu.MID_TABLE_VIEW_ROW_LABELS:
784 showRowLabels = getApplication().getMenuItem(menu.getId()).getChecked();
785 break;
786 case TMenu.MID_TABLE_VIEW_COLUMN_LABELS:
787 showColumnLabels = getApplication().getMenuItem(menu.getId()).getChecked();
788 break;
789 case TMenu.MID_TABLE_VIEW_HIGHLIGHT_ROW:
790 highlightRow = getApplication().getMenuItem(menu.getId()).getChecked();
791 break;
792 case TMenu.MID_TABLE_VIEW_HIGHLIGHT_COLUMN:
793 highlightColumn = getApplication().getMenuItem(menu.getId()).getChecked();
794 break;
1dac6b8d
KL
795 case TMenu.MID_TABLE_BORDER_NONE:
796 case TMenu.MID_TABLE_BORDER_ALL:
797 case TMenu.MID_TABLE_BORDER_RIGHT:
798 case TMenu.MID_TABLE_BORDER_LEFT:
799 case TMenu.MID_TABLE_BORDER_TOP:
800 case TMenu.MID_TABLE_BORDER_BOTTOM:
801 case TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM:
802 case TMenu.MID_TABLE_BORDER_THICK_BOTTOM:
803 case TMenu.MID_TABLE_DELETE_LEFT:
804 case TMenu.MID_TABLE_DELETE_UP:
805 case TMenu.MID_TABLE_DELETE_ROW:
806 case TMenu.MID_TABLE_DELETE_COLUMN:
807 case TMenu.MID_TABLE_INSERT_LEFT:
808 case TMenu.MID_TABLE_INSERT_RIGHT:
809 case TMenu.MID_TABLE_INSERT_ABOVE:
810 case TMenu.MID_TABLE_INSERT_BELOW:
811 break;
812 case TMenu.MID_TABLE_COLUMN_NARROW:
813 columns.get(selectedColumn).width--;
814 for (Cell cell: getSelectedColumn().cells) {
815 cell.setWidth(columns.get(selectedColumn).width);
759cb83e 816 cell.field.setWidth(columns.get(selectedColumn).width);
1dac6b8d 817 }
9c172016 818 for (int i = selectedColumn + 1; i < columns.size(); i++) {
11cedc9a 819 columns.get(i).setX(columns.get(i).getX() - 1);
9c172016 820 }
11cedc9a 821 alignGrid();
1dac6b8d
KL
822 break;
823 case TMenu.MID_TABLE_COLUMN_WIDEN:
824 columns.get(selectedColumn).width++;
825 for (Cell cell: getSelectedColumn().cells) {
826 cell.setWidth(columns.get(selectedColumn).width);
759cb83e 827 cell.field.setWidth(columns.get(selectedColumn).width);
1dac6b8d 828 }
9c172016 829 for (int i = selectedColumn + 1; i < columns.size(); i++) {
11cedc9a 830 columns.get(i).setX(columns.get(i).getX() + 1);
9c172016 831 }
11cedc9a 832 alignGrid();
1dac6b8d
KL
833 break;
834 case TMenu.MID_TABLE_FILE_SAVE_CSV:
759cb83e
KL
835 // TODO
836 break;
1dac6b8d 837 case TMenu.MID_TABLE_FILE_SAVE_TEXT:
759cb83e 838 // TODO
1dac6b8d
KL
839 break;
840 default:
841 super.onMenu(menu);
842 }
77961919 843
11cedc9a 844 alignGrid();
1dac6b8d
KL
845 }
846
847 // ------------------------------------------------------------------------
848 // TWidget ----------------------------------------------------------------
849 // ------------------------------------------------------------------------
850
68ee64d0 851 /**
77961919 852 * Draw the table row/column labels, and borders.
68ee64d0
KL
853 */
854 @Override
855 public void draw() {
77961919
KL
856 CellAttributes labelColor = getTheme().getColor("ttable.label");
857 CellAttributes labelColorSelected = getTheme().getColor("ttable.label.selected");
68ee64d0
KL
858 CellAttributes borderColor = getTheme().getColor("ttable.border");
859
860 // Column labels.
861 if (showColumnLabels == true) {
68ee64d0 862 for (int i = left; i < columns.size(); i++) {
2e1384cc
KL
863 if (columns.get(i).get(top).isVisible() == false) {
864 break;
865 }
866 putStringXY(columns.get(i).get(top).getX(), 0,
759cb83e 867 String.format(" %-" +
73162e7f 868 (columns.get(i).width - 2)
759cb83e 869 + "s ", columns.get(i).label),
77961919 870 (i == selectedColumn ? labelColorSelected : labelColor));
68ee64d0
KL
871 }
872 }
873
874 // Row labels.
875 if (showRowLabels == true) {
68ee64d0 876 for (int i = top; i < rows.size(); i++) {
2e1384cc
KL
877 if (rows.get(i).get(left).isVisible() == false) {
878 break;
879 }
880 putStringXY(0, rows.get(i).get(left).getY(),
881 String.format(" %-6s ", rows.get(i).label),
77961919 882 (i == selectedRow ? labelColorSelected : labelColor));
68ee64d0
KL
883 }
884 }
885
886 // Now draw the window borders.
887 super.draw();
888 }
889
1dac6b8d
KL
890 // ------------------------------------------------------------------------
891 // TTable -----------------------------------------------------------------
892 // ------------------------------------------------------------------------
893
894 /**
895 * Get the currently-selected cell.
896 *
897 * @return the selected cell
898 */
899 public Cell getSelectedCell() {
900 assert (rows.get(selectedRow) != null);
901 assert (rows.get(selectedRow).get(selectedColumn) != null);
902 assert (columns.get(selectedColumn) != null);
903 assert (columns.get(selectedColumn).get(selectedRow) != null);
904 assert (rows.get(selectedRow).get(selectedColumn) ==
905 columns.get(selectedColumn).get(selectedRow));
906
907 return (columns.get(selectedColumn).get(selectedRow));
908 }
909
910 /**
911 * Get the currently-selected column.
912 *
913 * @return the selected column
914 */
915 public Column getSelectedColumn() {
916 assert (selectedColumn >= 0);
917 assert (columns.size() > selectedColumn);
918 assert (columns.get(selectedColumn) != null);
919 return columns.get(selectedColumn);
920 }
921
922 /**
923 * Get the currently-selected row.
924 *
925 * @return the selected row
926 */
927 public Row getSelectedRow() {
928 assert (selectedRow >= 0);
929 assert (rows.size() > selectedRow);
930 assert (rows.get(selectedRow) != null);
931 return rows.get(selectedRow);
932 }
933
759cb83e
KL
934 /**
935 * Get the currently-selected column number. 0 is the left-most column.
936 *
937 * @return the selected column number
938 */
939 public int getSelectedColumnNumber() {
940 return selectedColumn;
941 }
942
943 /**
944 * Set the currently-selected column number. 0 is the left-most column.
945 *
946 * @param column the column number to select
947 */
948 public void setSelectedColumnNumber(final int column) {
949 if ((column < 0) || (column > columns.size() - 1)) {
950 throw new IndexOutOfBoundsException("Column count is " +
951 columns.size() + ", requested index " + column);
952 }
953 selectedColumn = column;
954 activate(columns.get(selectedColumn).get(selectedRow));
11cedc9a 955 alignGrid();
759cb83e
KL
956 }
957
958 /**
959 * Get the currently-selected row number. 0 is the top-most row.
960 *
961 * @return the selected row number
962 */
963 public int getSelectedRowNumber() {
964 return selectedRow;
965 }
966
967 /**
968 * Set the currently-selected row number. 0 is the left-most column.
969 *
970 * @param row the row number to select
971 */
972 public void setSelectedRowNumber(final int row) {
973 if ((row < 0) || (row > rows.size() - 1)) {
974 throw new IndexOutOfBoundsException("Row count is " +
975 rows.size() + ", requested index " + row);
976 }
977 selectedRow = row;
978 activate(columns.get(selectedColumn).get(selectedRow));
11cedc9a 979 alignGrid();
759cb83e
KL
980 }
981
982 /**
983 * Get the number of columns.
984 *
985 * @return the number of columns
986 */
987 public int getColumnCount() {
988 return columns.size();
989 }
990
991 /**
992 * Get the number of rows.
993 *
994 * @return the number of rows
995 */
996 public int getRowCount() {
997 return rows.size();
998 }
999
9c172016
KL
1000 /**
1001 * Align the grid so that the selected cell is fully visible.
1002 */
11cedc9a
KL
1003 private void alignGrid() {
1004 int viewColumns = getWidth();
1005 if (showRowLabels == true) {
1006 viewColumns -= ROW_LABEL_WIDTH;
1007 }
1008 if (leftBorder != Border.NONE) {
1009 viewColumns--;
1010 }
1011 int viewRows = getHeight();
1012 if (showColumnLabels == true) {
1013 viewRows -= COLUMN_LABEL_HEIGHT;
1014 }
1015 if (topBorder != Border.NONE) {
1016 viewRows--;
1017 }
73162e7f 1018
11cedc9a
KL
1019 // If we pushed left or right, adjust the box to include the new
1020 // selected cell.
73162e7f 1021 if (selectedColumn < left) {
11cedc9a 1022 left = selectedColumn - 1;
73162e7f 1023 }
11cedc9a
KL
1024 if (left < 0) {
1025 left = 0;
73162e7f 1026 }
11cedc9a
KL
1027 if (selectedRow < top) {
1028 top = selectedRow - 1;
73162e7f 1029 }
11cedc9a
KL
1030 if (top < 0) {
1031 top = 0;
73162e7f 1032 }
68ee64d0 1033
2e1384cc 1034 /*
11cedc9a
KL
1035 * viewColumns and viewRows now contain the available columns and
1036 * rows available to view the selected cell. We adjust left and top
1037 * to ensure the selected cell is within view, and then make all
1038 * cells outside the box between (left, top) and (right, bottom)
1039 * invisible.
1040 *
1041 * We need to calculate right and bottom now.
2e1384cc 1042 */
11cedc9a
KL
1043 int right = left;
1044
1045 boolean done = false;
1046 while (!done) {
1047 int rightCellX = (showRowLabels ? ROW_LABEL_WIDTH : 0);
1048 int maxCellX = rightCellX + viewColumns;
1049 right = left;
1050 boolean selectedIsVisible = false;
1051 int selectedX = 0;
1052 for (int x = left; x < columns.size(); x++) {
1053 if (x == selectedColumn) {
1054 selectedX = rightCellX;
1055 if (selectedX + columns.get(x).width + 1 <= maxCellX) {
1056 selectedIsVisible = true;
1057 }
759cb83e 1058 }
11cedc9a
KL
1059 rightCellX += columns.get(x).width + 1;
1060 if (rightCellX >= maxCellX) {
1061 break;
68ee64d0 1062 }
11cedc9a 1063 right++;
68ee64d0 1064 }
11cedc9a
KL
1065 if (right < selectedColumn) {
1066 // selectedColumn is outside the view range. Push left over,
1067 // and calculate again.
1068 left++;
1069 } else if (left == selectedColumn) {
1070 // selectedColumn doesn't fit inside the view range, but we
1071 // can't go over any further either. Bail out.
1072 done = true;
1073 } else if (selectedIsVisible == false) {
1074 // selectedColumn doesn't fit inside the view range, continue
1075 // on.
73162e7f 1076 left++;
11cedc9a
KL
1077 } else {
1078 // selectedColumn is fully visible, all done.
1079 assert (selectedIsVisible == true);
1080 done = true;
1081 }
73162e7f 1082
11cedc9a 1083 } // while (!done)
73162e7f 1084
11cedc9a
KL
1085 // We have the left/right range correct, set cell visibility and
1086 // column X positions.
1087 int leftCellX = showRowLabels ? ROW_LABEL_WIDTH : 0;
1088 for (int x = 0; x < columns.size(); x++) {
1089 if ((x < left) || (x > right)) {
1090 for (int i = 0; i < rows.size(); i++) {
1091 columns.get(x).get(i).setVisible(false);
1092 columns.get(x).setX(getWidth() + 1);
68ee64d0 1093 }
11cedc9a 1094 continue;
68ee64d0 1095 }
11cedc9a
KL
1096 for (int i = 0; i < rows.size(); i++) {
1097 columns.get(x).get(i).setVisible(true);
68ee64d0 1098 }
11cedc9a
KL
1099 columns.get(x).setX(leftCellX);
1100 leftCellX += columns.get(x).width + 1;
68ee64d0 1101 }
68ee64d0 1102
11cedc9a 1103 int bottom = top;
77961919 1104
11cedc9a
KL
1105 done = false;
1106 while (!done) {
1107 int bottomCellY = (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0);
1108 int maxCellY = bottomCellY + viewRows;
1109 bottom = top;
1110 for (int y = top; y < rows.size(); y++) {
1111 bottomCellY += rows.get(y).height;
1112 if (bottomCellY >= maxCellY) {
1113 break;
68ee64d0 1114 }
11cedc9a 1115 bottom++;
68ee64d0 1116 }
11cedc9a
KL
1117 if (bottom < selectedRow) {
1118 // selectedRow is outside the view range. Push top down, and
1119 // calculate again.
73162e7f 1120 top++;
11cedc9a
KL
1121 } else {
1122 // selectedRow is inside the view range, done.
1123 done = true;
1124 }
1125 } // while (!done)
1126
1127 // We have the top/bottom range correct, set cell visibility and
1128 // row Y positions.
1129 int topCellY = showColumnLabels ? COLUMN_LABEL_HEIGHT : 0;
1130 for (int y = 0; y < rows.size(); y++) {
1131 if ((y < top) || (y > bottom)) {
1132 for (int i = 0; i < columns.size(); i++) {
1133 rows.get(y).get(i).setVisible(false);
68ee64d0 1134 }
11cedc9a
KL
1135 rows.get(y).setY(getHeight() + 1);
1136 continue;
68ee64d0 1137 }
11cedc9a
KL
1138 for (int i = 0; i < columns.size(); i++) {
1139 rows.get(y).get(i).setVisible(true);
1140 }
1141 rows.get(y).setY(topCellY);
1142 topCellY += rows.get(y).height;
1143 }
9c172016
KL
1144
1145 }
1146
759cb83e
KL
1147 /**
1148 * Save contents to file.
1149 *
1150 * @param filename file to save to
1151 * @throws IOException if a java.io operation throws
1152 */
1153 public void saveToFilename(final String filename) throws IOException {
1154 // TODO
1155 }
1156
1dac6b8d 1157}