stubs for drawing borders
[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 795 case TMenu.MID_TABLE_BORDER_NONE:
91b3175a
KL
796 if (selectedRow == 0) {
797 topBorder = Border.NONE;
798 }
799 if (selectedColumn == 0) {
800 leftBorder = Border.NONE;
801 }
802 columns.get(selectedColumn).rightBorder = Border.NONE;
803 rows.get(selectedRow).bottomBorder = Border.NONE;
804 rows.get(selectedRow).height = 1;
805 break;
1dac6b8d 806 case TMenu.MID_TABLE_BORDER_ALL:
91b3175a
KL
807 if (selectedRow == 0) {
808 topBorder = Border.SINGLE;
809 }
810 if (selectedColumn == 0) {
811 leftBorder = Border.SINGLE;
812 }
813 columns.get(selectedColumn).rightBorder = Border.SINGLE;
814 rows.get(selectedRow).bottomBorder = Border.SINGLE;
815 rows.get(selectedRow).height = 2;
816 break;
1dac6b8d 817 case TMenu.MID_TABLE_BORDER_RIGHT:
91b3175a
KL
818 columns.get(selectedColumn).rightBorder = Border.SINGLE;
819 break;
1dac6b8d 820 case TMenu.MID_TABLE_BORDER_LEFT:
91b3175a
KL
821 if (selectedColumn == 0) {
822 leftBorder = Border.SINGLE;
823 } else {
824 columns.get(selectedColumn - 1).rightBorder = Border.SINGLE;
825 }
826 break;
1dac6b8d 827 case TMenu.MID_TABLE_BORDER_TOP:
91b3175a
KL
828 if (selectedRow == 0) {
829 topBorder = Border.SINGLE;
830 } else {
831 rows.get(selectedRow - 1).bottomBorder = Border.SINGLE;
832 rows.get(selectedRow - 1).height = 2;
833 }
834 break;
1dac6b8d 835 case TMenu.MID_TABLE_BORDER_BOTTOM:
91b3175a
KL
836 rows.get(selectedRow).bottomBorder = Border.SINGLE;
837 rows.get(selectedRow).height = 2;
838 break;
1dac6b8d 839 case TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM:
91b3175a
KL
840 rows.get(selectedRow).bottomBorder = Border.DOUBLE;
841 rows.get(selectedRow).height = 2;
842 break;
1dac6b8d 843 case TMenu.MID_TABLE_BORDER_THICK_BOTTOM:
91b3175a
KL
844 rows.get(selectedRow).bottomBorder = Border.THICK;
845 rows.get(selectedRow).height = 2;
846 break;
1dac6b8d
KL
847 case TMenu.MID_TABLE_DELETE_LEFT:
848 case TMenu.MID_TABLE_DELETE_UP:
849 case TMenu.MID_TABLE_DELETE_ROW:
850 case TMenu.MID_TABLE_DELETE_COLUMN:
851 case TMenu.MID_TABLE_INSERT_LEFT:
852 case TMenu.MID_TABLE_INSERT_RIGHT:
853 case TMenu.MID_TABLE_INSERT_ABOVE:
854 case TMenu.MID_TABLE_INSERT_BELOW:
855 break;
856 case TMenu.MID_TABLE_COLUMN_NARROW:
857 columns.get(selectedColumn).width--;
858 for (Cell cell: getSelectedColumn().cells) {
859 cell.setWidth(columns.get(selectedColumn).width);
759cb83e 860 cell.field.setWidth(columns.get(selectedColumn).width);
1dac6b8d 861 }
9c172016 862 for (int i = selectedColumn + 1; i < columns.size(); i++) {
11cedc9a 863 columns.get(i).setX(columns.get(i).getX() - 1);
9c172016 864 }
1dac6b8d
KL
865 break;
866 case TMenu.MID_TABLE_COLUMN_WIDEN:
867 columns.get(selectedColumn).width++;
868 for (Cell cell: getSelectedColumn().cells) {
869 cell.setWidth(columns.get(selectedColumn).width);
759cb83e 870 cell.field.setWidth(columns.get(selectedColumn).width);
1dac6b8d 871 }
9c172016 872 for (int i = selectedColumn + 1; i < columns.size(); i++) {
11cedc9a 873 columns.get(i).setX(columns.get(i).getX() + 1);
9c172016 874 }
1dac6b8d
KL
875 break;
876 case TMenu.MID_TABLE_FILE_SAVE_CSV:
759cb83e
KL
877 // TODO
878 break;
1dac6b8d 879 case TMenu.MID_TABLE_FILE_SAVE_TEXT:
759cb83e 880 // TODO
1dac6b8d
KL
881 break;
882 default:
883 super.onMenu(menu);
884 }
77961919 885
91b3175a 886 // Fix/redraw the display.
11cedc9a 887 alignGrid();
1dac6b8d
KL
888 }
889
890 // ------------------------------------------------------------------------
891 // TWidget ----------------------------------------------------------------
892 // ------------------------------------------------------------------------
893
68ee64d0 894 /**
77961919 895 * Draw the table row/column labels, and borders.
68ee64d0
KL
896 */
897 @Override
898 public void draw() {
77961919
KL
899 CellAttributes labelColor = getTheme().getColor("ttable.label");
900 CellAttributes labelColorSelected = getTheme().getColor("ttable.label.selected");
68ee64d0
KL
901 CellAttributes borderColor = getTheme().getColor("ttable.border");
902
903 // Column labels.
904 if (showColumnLabels == true) {
68ee64d0 905 for (int i = left; i < columns.size(); i++) {
2e1384cc
KL
906 if (columns.get(i).get(top).isVisible() == false) {
907 break;
908 }
91b3175a
KL
909 putStringXY(columns.get(i).get(top).getX(),
910 (topBorder == Border.NONE ? 0 : 1),
759cb83e 911 String.format(" %-" +
73162e7f 912 (columns.get(i).width - 2)
759cb83e 913 + "s ", columns.get(i).label),
77961919 914 (i == selectedColumn ? labelColorSelected : labelColor));
68ee64d0
KL
915 }
916 }
917
918 // Row labels.
919 if (showRowLabels == true) {
68ee64d0 920 for (int i = top; i < rows.size(); i++) {
2e1384cc
KL
921 if (rows.get(i).get(left).isVisible() == false) {
922 break;
923 }
924 putStringXY(0, rows.get(i).get(left).getY(),
925 String.format(" %-6s ", rows.get(i).label),
77961919 926 (i == selectedRow ? labelColorSelected : labelColor));
68ee64d0
KL
927 }
928 }
929
91b3175a
KL
930 // Draw vertical borders.
931 if (leftBorder == Border.SINGLE) {
932 vLineXY((showRowLabels ? ROW_LABEL_WIDTH : 0), 0,
933 getHeight(), '\u2502', borderColor);
934 }
935 for (int i = left; i < columns.size(); i++) {
936 if (columns.get(i).get(top).isVisible() == false) {
937 break;
938 }
939 if (columns.get(i).rightBorder == Border.SINGLE) {
940 vLineXY(columns.get(i).getX() + columns.get(i).width,
941 (topBorder == Border.NONE ? 0 : 1),
942 getHeight(), '\u2502', borderColor);
943 }
944 }
945
946 // Draw horizontal borders.
947 if (topBorder == Border.SINGLE) {
948 hLineXY((showRowLabels ? ROW_LABEL_WIDTH : 0), 0,
949 getWidth(), '\u2500', borderColor);
950 }
951 for (int i = top; i < rows.size(); i++) {
952 if (rows.get(i).get(left).isVisible() == false) {
953 break;
954 }
955 if (rows.get(i).bottomBorder == Border.SINGLE) {
956 hLineXY((leftBorder == Border.NONE ? 0 : 1) +
957 (showRowLabels ? ROW_LABEL_WIDTH : 0),
958 rows.get(i).getY() + rows.get(i).height - 1,
959 getWidth(), '\u2500', borderColor);
960 } else if (rows.get(i).bottomBorder == Border.DOUBLE) {
961 hLineXY((leftBorder == Border.NONE ? 0 : 1) +
962 (showRowLabels ? ROW_LABEL_WIDTH : 0),
963 rows.get(i).getY() + rows.get(i).height - 1,
964 getWidth(), '\u2550', borderColor);
965 } else if (rows.get(i).bottomBorder == Border.THICK) {
966 hLineXY((leftBorder == Border.NONE ? 0 : 1) +
967 (showRowLabels ? ROW_LABEL_WIDTH : 0),
968 rows.get(i).getY() + rows.get(i).height - 1,
969 getWidth(), '\u2580', borderColor);
970 }
971 }
972 // Top-left corner if needed
973 if ((topBorder == Border.SINGLE) && (leftBorder == Border.SINGLE)) {
974 putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0), 0,
975 '\u250c', borderColor);
976 }
977
978 // TODO: draw the correct corners between rows and columns
979
68ee64d0
KL
980 // Now draw the window borders.
981 super.draw();
982 }
983
1dac6b8d
KL
984 // ------------------------------------------------------------------------
985 // TTable -----------------------------------------------------------------
986 // ------------------------------------------------------------------------
987
988 /**
989 * Get the currently-selected cell.
990 *
991 * @return the selected cell
992 */
993 public Cell getSelectedCell() {
994 assert (rows.get(selectedRow) != null);
995 assert (rows.get(selectedRow).get(selectedColumn) != null);
996 assert (columns.get(selectedColumn) != null);
997 assert (columns.get(selectedColumn).get(selectedRow) != null);
998 assert (rows.get(selectedRow).get(selectedColumn) ==
999 columns.get(selectedColumn).get(selectedRow));
1000
1001 return (columns.get(selectedColumn).get(selectedRow));
1002 }
1003
1004 /**
1005 * Get the currently-selected column.
1006 *
1007 * @return the selected column
1008 */
1009 public Column getSelectedColumn() {
1010 assert (selectedColumn >= 0);
1011 assert (columns.size() > selectedColumn);
1012 assert (columns.get(selectedColumn) != null);
1013 return columns.get(selectedColumn);
1014 }
1015
1016 /**
1017 * Get the currently-selected row.
1018 *
1019 * @return the selected row
1020 */
1021 public Row getSelectedRow() {
1022 assert (selectedRow >= 0);
1023 assert (rows.size() > selectedRow);
1024 assert (rows.get(selectedRow) != null);
1025 return rows.get(selectedRow);
1026 }
1027
759cb83e
KL
1028 /**
1029 * Get the currently-selected column number. 0 is the left-most column.
1030 *
1031 * @return the selected column number
1032 */
1033 public int getSelectedColumnNumber() {
1034 return selectedColumn;
1035 }
1036
1037 /**
1038 * Set the currently-selected column number. 0 is the left-most column.
1039 *
1040 * @param column the column number to select
1041 */
1042 public void setSelectedColumnNumber(final int column) {
1043 if ((column < 0) || (column > columns.size() - 1)) {
1044 throw new IndexOutOfBoundsException("Column count is " +
1045 columns.size() + ", requested index " + column);
1046 }
1047 selectedColumn = column;
1048 activate(columns.get(selectedColumn).get(selectedRow));
11cedc9a 1049 alignGrid();
759cb83e
KL
1050 }
1051
1052 /**
1053 * Get the currently-selected row number. 0 is the top-most row.
1054 *
1055 * @return the selected row number
1056 */
1057 public int getSelectedRowNumber() {
1058 return selectedRow;
1059 }
1060
1061 /**
1062 * Set the currently-selected row number. 0 is the left-most column.
1063 *
1064 * @param row the row number to select
1065 */
1066 public void setSelectedRowNumber(final int row) {
1067 if ((row < 0) || (row > rows.size() - 1)) {
1068 throw new IndexOutOfBoundsException("Row count is " +
1069 rows.size() + ", requested index " + row);
1070 }
1071 selectedRow = row;
1072 activate(columns.get(selectedColumn).get(selectedRow));
11cedc9a 1073 alignGrid();
759cb83e
KL
1074 }
1075
1076 /**
1077 * Get the number of columns.
1078 *
1079 * @return the number of columns
1080 */
1081 public int getColumnCount() {
1082 return columns.size();
1083 }
1084
1085 /**
1086 * Get the number of rows.
1087 *
1088 * @return the number of rows
1089 */
1090 public int getRowCount() {
1091 return rows.size();
1092 }
1093
9c172016
KL
1094 /**
1095 * Align the grid so that the selected cell is fully visible.
1096 */
11cedc9a
KL
1097 private void alignGrid() {
1098 int viewColumns = getWidth();
1099 if (showRowLabels == true) {
1100 viewColumns -= ROW_LABEL_WIDTH;
1101 }
1102 if (leftBorder != Border.NONE) {
1103 viewColumns--;
1104 }
1105 int viewRows = getHeight();
1106 if (showColumnLabels == true) {
1107 viewRows -= COLUMN_LABEL_HEIGHT;
1108 }
1109 if (topBorder != Border.NONE) {
1110 viewRows--;
1111 }
73162e7f 1112
11cedc9a
KL
1113 // If we pushed left or right, adjust the box to include the new
1114 // selected cell.
73162e7f 1115 if (selectedColumn < left) {
11cedc9a 1116 left = selectedColumn - 1;
73162e7f 1117 }
11cedc9a
KL
1118 if (left < 0) {
1119 left = 0;
73162e7f 1120 }
11cedc9a
KL
1121 if (selectedRow < top) {
1122 top = selectedRow - 1;
73162e7f 1123 }
11cedc9a
KL
1124 if (top < 0) {
1125 top = 0;
73162e7f 1126 }
68ee64d0 1127
2e1384cc 1128 /*
11cedc9a
KL
1129 * viewColumns and viewRows now contain the available columns and
1130 * rows available to view the selected cell. We adjust left and top
1131 * to ensure the selected cell is within view, and then make all
1132 * cells outside the box between (left, top) and (right, bottom)
1133 * invisible.
1134 *
1135 * We need to calculate right and bottom now.
2e1384cc 1136 */
11cedc9a
KL
1137 int right = left;
1138
1139 boolean done = false;
1140 while (!done) {
1141 int rightCellX = (showRowLabels ? ROW_LABEL_WIDTH : 0);
91b3175a
KL
1142 if (leftBorder != Border.NONE) {
1143 rightCellX++;
1144 }
11cedc9a
KL
1145 int maxCellX = rightCellX + viewColumns;
1146 right = left;
1147 boolean selectedIsVisible = false;
1148 int selectedX = 0;
1149 for (int x = left; x < columns.size(); x++) {
1150 if (x == selectedColumn) {
1151 selectedX = rightCellX;
1152 if (selectedX + columns.get(x).width + 1 <= maxCellX) {
1153 selectedIsVisible = true;
1154 }
759cb83e 1155 }
11cedc9a
KL
1156 rightCellX += columns.get(x).width + 1;
1157 if (rightCellX >= maxCellX) {
1158 break;
68ee64d0 1159 }
11cedc9a 1160 right++;
68ee64d0 1161 }
11cedc9a
KL
1162 if (right < selectedColumn) {
1163 // selectedColumn is outside the view range. Push left over,
1164 // and calculate again.
1165 left++;
1166 } else if (left == selectedColumn) {
1167 // selectedColumn doesn't fit inside the view range, but we
1168 // can't go over any further either. Bail out.
1169 done = true;
1170 } else if (selectedIsVisible == false) {
1171 // selectedColumn doesn't fit inside the view range, continue
1172 // on.
73162e7f 1173 left++;
11cedc9a
KL
1174 } else {
1175 // selectedColumn is fully visible, all done.
1176 assert (selectedIsVisible == true);
1177 done = true;
1178 }
73162e7f 1179
11cedc9a 1180 } // while (!done)
73162e7f 1181
11cedc9a
KL
1182 // We have the left/right range correct, set cell visibility and
1183 // column X positions.
1184 int leftCellX = showRowLabels ? ROW_LABEL_WIDTH : 0;
91b3175a
KL
1185 if (leftBorder != Border.NONE) {
1186 leftCellX++;
1187 }
11cedc9a
KL
1188 for (int x = 0; x < columns.size(); x++) {
1189 if ((x < left) || (x > right)) {
1190 for (int i = 0; i < rows.size(); i++) {
1191 columns.get(x).get(i).setVisible(false);
1192 columns.get(x).setX(getWidth() + 1);
68ee64d0 1193 }
11cedc9a 1194 continue;
68ee64d0 1195 }
11cedc9a
KL
1196 for (int i = 0; i < rows.size(); i++) {
1197 columns.get(x).get(i).setVisible(true);
68ee64d0 1198 }
11cedc9a
KL
1199 columns.get(x).setX(leftCellX);
1200 leftCellX += columns.get(x).width + 1;
68ee64d0 1201 }
68ee64d0 1202
11cedc9a 1203 int bottom = top;
77961919 1204
11cedc9a
KL
1205 done = false;
1206 while (!done) {
1207 int bottomCellY = (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0);
91b3175a
KL
1208 if (topBorder != Border.NONE) {
1209 bottomCellY++;
1210 }
11cedc9a
KL
1211 int maxCellY = bottomCellY + viewRows;
1212 bottom = top;
1213 for (int y = top; y < rows.size(); y++) {
1214 bottomCellY += rows.get(y).height;
1215 if (bottomCellY >= maxCellY) {
1216 break;
68ee64d0 1217 }
11cedc9a 1218 bottom++;
68ee64d0 1219 }
11cedc9a
KL
1220 if (bottom < selectedRow) {
1221 // selectedRow is outside the view range. Push top down, and
1222 // calculate again.
73162e7f 1223 top++;
11cedc9a
KL
1224 } else {
1225 // selectedRow is inside the view range, done.
1226 done = true;
1227 }
1228 } // while (!done)
1229
1230 // We have the top/bottom range correct, set cell visibility and
1231 // row Y positions.
1232 int topCellY = showColumnLabels ? COLUMN_LABEL_HEIGHT : 0;
91b3175a
KL
1233 if (topBorder != Border.NONE) {
1234 topCellY++;
1235 }
11cedc9a
KL
1236 for (int y = 0; y < rows.size(); y++) {
1237 if ((y < top) || (y > bottom)) {
1238 for (int i = 0; i < columns.size(); i++) {
1239 rows.get(y).get(i).setVisible(false);
68ee64d0 1240 }
11cedc9a
KL
1241 rows.get(y).setY(getHeight() + 1);
1242 continue;
68ee64d0 1243 }
11cedc9a
KL
1244 for (int i = 0; i < columns.size(); i++) {
1245 rows.get(y).get(i).setVisible(true);
1246 }
1247 rows.get(y).setY(topCellY);
1248 topCellY += rows.get(y).height;
1249 }
9c172016
KL
1250
1251 }
1252
759cb83e
KL
1253 /**
1254 * Save contents to file.
1255 *
1256 * @param filename file to save to
1257 * @throws IOException if a java.io operation throws
1258 */
1259 public void saveToFilename(final String filename) throws IOException {
1260 // TODO
1261 }
1262
1dac6b8d 1263}