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
.io
.IOException
;
32 import java
.util
.ArrayList
;
33 import java
.util
.List
;
35 import jexer
.bits
.CellAttributes
;
36 import jexer
.event
.TKeypressEvent
;
37 import jexer
.event
.TMenuEvent
;
38 import jexer
.event
.TMouseEvent
;
39 import jexer
.event
.TResizeEvent
;
40 import jexer
.menu
.TMenu
;
41 import static jexer
.TKeypress
.*;
44 * TTableWidget is used to display and edit regular two-dimensional tables of
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.
51 public class TTableWidget
extends TWidget
{
53 // ------------------------------------------------------------------------
54 // Constants --------------------------------------------------------------
55 // ------------------------------------------------------------------------
58 * Available borders for cells.
67 * Single bar: \u2502 (vertical) and \u2500 (horizontal).
72 * Double bar: \u2551 (vertical) and \u2550 (horizontal).
77 * Thick bar: \u258C (vertical, left half block) and \u2580
78 * (horizontal, upper block).
86 private static final int ROW_LABEL_WIDTH
= 8;
89 * Column label height.
91 private static final int COLUMN_LABEL_HEIGHT
= 1;
94 * Column default width.
96 private static final int COLUMN_DEFAULT_WIDTH
= 8;
101 private static final int EXTRA_ROWS
= 10;
104 * Extra columns to add.
106 private static final int EXTRA_COLUMNS
= 10 * (8 + 1);
108 // ------------------------------------------------------------------------
109 // Variables --------------------------------------------------------------
110 // ------------------------------------------------------------------------
113 * The underlying data, organized as columns.
115 private ArrayList
<Column
> columns
= new ArrayList
<Column
>();
118 * The underlying data, organized as rows.
120 private ArrayList
<Row
> rows
= new ArrayList
<Row
>();
123 * The row in model corresponding to the top-left visible cell.
128 * The column in model corresponding to the top-left visible cell.
130 private int left
= 0;
133 * The row in model corresponding to the currently selected cell.
135 private int selectedRow
= 0;
138 * The column in model corresponding to the currently selected cell.
140 private int selectedColumn
= 0;
143 * If true, highlight the entire row of the currently-selected cell.
145 private boolean highlightRow
= false;
148 * If true, highlight the entire column of the currently-selected cell.
150 private boolean highlightColumn
= false;
153 * If true, show the row labels as the first column.
155 private boolean showRowLabels
= true;
158 * If true, show the column labels as the first row.
160 private boolean showColumnLabels
= true;
163 * The top border for the first row.
165 private Border topBorder
= Border
.NONE
;
168 * The left border for the first column.
170 private Border leftBorder
= Border
.NONE
;
173 * Column represents a column of cells.
175 public class Column
{
178 * X position of this column.
185 private int width
= COLUMN_DEFAULT_WIDTH
;
188 * The cells of this column.
190 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
195 private String label
= "";
198 * The right border for this column.
200 private Border rightBorder
= Border
.NONE
;
203 * Constructor sets label to lettered column.
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.
209 StringBuilder sb
= new StringBuilder();
211 sb
.append((char) ('A' + (col
% 26)));
217 label
= sb
.reverse().toString();
221 * Add an entry to this column.
223 * @param cell the cell to add
225 public void add(final Cell cell
) {
230 * Get an entry from this column.
232 * @param row the entry index to get
233 * @return the cell at row
235 public Cell
get(final int row
) {
236 return cells
.get(row
);
240 * Get the X position of the cells in this column.
242 * @return the position
249 * Set the X position of the cells in this column.
251 * @param x the position
253 public void setX(final int x
) {
254 for (Cell cell
: cells
) {
263 * Row represents a row of cells.
268 * Y position of this row.
275 private int height
= 1;
278 * The cells of this row.
280 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
285 private String label
= "";
288 * The bottom border for this row.
290 private Border bottomBorder
= Border
.NONE
;
293 * Constructor sets label to numbered row.
295 * @param row row number to use for this row
298 label
= Integer
.toString(row
);
302 * Add an entry to this column.
304 * @param cell the cell to add
306 public void add(final Cell cell
) {
311 * Get an entry from this row.
313 * @param column the entry index to get
314 * @return the cell at column
316 public Cell
get(final int column
) {
317 return cells
.get(column
);
320 * Get the Y position of the cells in this column.
322 * @return the position
329 * Set the Y position of the cells in this column.
331 * @param y the position
333 public void setY(final int y
) {
334 for (Cell cell
: cells
) {
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
347 public class Cell
extends TWidget
{
349 // --------------------------------------------------------------------
350 // Variables ----------------------------------------------------------
351 // --------------------------------------------------------------------
354 * The field containing the cell's data.
356 private TField field
;
359 * The column of this cell.
364 * The row of this cell.
369 * If true, the cell is being edited.
371 private boolean isEditing
= false;
374 * Text of field before editing.
376 private String fieldText
;
378 // --------------------------------------------------------------------
379 // Constructors -------------------------------------------------------
380 // --------------------------------------------------------------------
383 * Public constructor.
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
393 public Cell(final TTableWidget parent
, final int x
, final int y
,
394 final int width
, final int height
, final int column
,
397 super(parent
, x
, y
, width
, height
);
398 this.column
= column
;
401 field
= addField(0, 0, width
, false);
402 field
.setEnabled(false);
403 field
.setBackgroundChar(' ');
406 // --------------------------------------------------------------------
407 // Event handlers -----------------------------------------------------
408 // --------------------------------------------------------------------
411 * Handle mouse double-click events.
413 * @param mouse mouse double-click event
416 public void onMouseDoubleClick(final TMouseEvent mouse
) {
417 // Use TWidget's code to pass the event to the children.
418 super.onMouseDown(mouse
);
420 // Double-click means to start editing.
421 fieldText
= field
.getText();
423 field
.setEnabled(true);
427 // Let the table know that I was activated.
428 ((TTableWidget
) getParent()).selectedRow
= row
;
429 ((TTableWidget
) getParent()).selectedColumn
= column
;
430 ((TTableWidget
) getParent()).alignGrid();
435 * Handle mouse press events.
437 * @param mouse mouse button press event
440 public void onMouseDown(final TMouseEvent mouse
) {
441 // Use TWidget's code to pass the event to the children.
442 super.onMouseDown(mouse
);
445 // Let the table know that I was activated.
446 ((TTableWidget
) getParent()).selectedRow
= row
;
447 ((TTableWidget
) getParent()).selectedColumn
= column
;
448 ((TTableWidget
) getParent()).alignGrid();
453 * Handle mouse release events.
455 * @param mouse mouse button release event
458 public void onMouseUp(final TMouseEvent mouse
) {
459 // Use TWidget's code to pass the event to the children.
460 super.onMouseDown(mouse
);
463 // Let the table know that I was activated.
464 ((TTableWidget
) getParent()).selectedRow
= row
;
465 ((TTableWidget
) getParent()).selectedColumn
= column
;
466 ((TTableWidget
) getParent()).alignGrid();
473 * @param keypress keystroke event
476 public void onKeypress(final TKeypressEvent keypress
) {
477 // System.err.println("Cell onKeypress: " + keypress);
480 if (keypress
.equals(kbEsc
)) {
481 // ESC cancels the edit.
482 field
.setText(fieldText
);
484 field
.setEnabled(false);
487 if (keypress
.equals(kbEnter
)) {
488 // Enter ends editing.
489 fieldText
= field
.getText();
491 field
.setEnabled(false);
494 // Pass down to field.
495 super.onKeypress(keypress
);
498 if (keypress
.equals(kbEnter
) || keypress
.equals(kbF2
)) {
499 // Enter or F2 starts editing.
500 fieldText
= field
.getText();
502 field
.setEnabled(true);
508 // --------------------------------------------------------------------
509 // TWidget ------------------------------------------------------------
510 // --------------------------------------------------------------------
517 TTableWidget table
= (TTableWidget
) getParent();
519 if (isAbsoluteActive()) {
521 field
.setActiveColorKey("tfield.active");
522 field
.setInactiveColorKey("tfield.inactive");
524 field
.setActiveColorKey("ttable.selected");
525 field
.setInactiveColorKey("ttable.selected");
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)))
534 field
.setActiveColorKey("ttable.active");
535 field
.setInactiveColorKey("ttable.active");
537 field
.setActiveColorKey("ttable.active");
538 field
.setInactiveColorKey("ttable.inactive");
541 assert (isVisible() == true);
546 // --------------------------------------------------------------------
547 // TTable.Cell --------------------------------------------------------
548 // --------------------------------------------------------------------
555 public final String
getText() {
556 return field
.getText();
562 * @param text the new field text
564 public void setText(final String text
) {
570 // ------------------------------------------------------------------------
571 // Constructors -----------------------------------------------------------
572 // ------------------------------------------------------------------------
575 * Public constructor.
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
583 public TTableWidget(final TWidget parent
, final int x
, final int y
,
584 final int width
, final int height
) {
586 super(parent
, x
, y
, width
, height
);
588 // Initialize the starting row and column.
589 rows
.add(new Row(0));
590 columns
.add(new Column(0));
592 // Place a grid of cells that fit in this space.
594 for (int i
= 0; i
< height
+ EXTRA_ROWS
; i
+= rows
.get(0).height
) {
596 for (int j
= 0; j
< width
+ EXTRA_COLUMNS
;
597 j
+= columns
.get(0).width
) {
599 Cell cell
= new Cell(this, 0, 0, /* j, i, */ columns
.get(0).width
,
600 rows
.get(0).height
, column
, row
);
602 // DEBUG: set a grid of cell index labels
604 cell
.setText("" + row
+ " " + column
);
605 rows
.get(row
).add(cell
);
606 columns
.get(column
).add(cell
);
608 (j
+ columns
.get(0).width
< width
+ EXTRA_COLUMNS
)
610 columns
.add(new Column(column
+ 1));
614 if (i
+ rows
.get(0).height
< height
+ EXTRA_ROWS
) {
615 rows
.add(new Row(row
+ 1));
619 for (int i
= 0; i
< rows
.size(); i
++) {
620 rows
.get(i
).setY(i
+ (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0));
622 for (int j
= 0; j
< columns
.size(); j
++) {
623 columns
.get(j
).setX((j
* COLUMN_DEFAULT_WIDTH
) +
624 (showRowLabels ? ROW_LABEL_WIDTH
: 0));
626 activate(columns
.get(selectedColumn
).get(selectedRow
));
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
);
643 // ------------------------------------------------------------------------
644 // Event handlers ---------------------------------------------------------
645 // ------------------------------------------------------------------------
648 * Handle mouse press events.
650 * @param mouse mouse button press event
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
);
660 keyEvent
= new TKeypressEvent(kbDown
);
662 for (int i
= 0; i
< 3; i
++) {
663 onKeypress(keyEvent
);
668 // Use TWidget's code to pass the event to the children.
669 super.onMouseDown(mouse
);
675 * @param keypress keystroke event
678 public void onKeypress(final TKeypressEvent keypress
) {
679 if (keypress
.equals(kbTab
)
680 || keypress
.equals(kbShiftTab
)
682 // Squash tab and back-tab. They don't make sense in the TTable
687 // If editing, pass to that cell and do nothing else.
688 if (getSelectedCell().isEditing
) {
689 super.onKeypress(keypress
);
693 if (keypress
.equals(kbLeft
)) {
695 if (selectedColumn
> 0) {
698 activate(columns
.get(selectedColumn
).get(selectedRow
));
699 } else if (keypress
.equals(kbRight
)) {
701 if (selectedColumn
< columns
.size() - 1) {
704 activate(columns
.get(selectedColumn
).get(selectedRow
));
705 } else if (keypress
.equals(kbUp
)) {
707 if (selectedRow
> 0) {
710 activate(columns
.get(selectedColumn
).get(selectedRow
));
711 } else if (keypress
.equals(kbDown
)) {
713 if (selectedRow
< rows
.size() - 1) {
716 activate(columns
.get(selectedColumn
).get(selectedRow
));
717 } else if (keypress
.equals(kbHome
)) {
718 // Home - leftmost column
720 activate(columns
.get(selectedColumn
).get(selectedRow
));
721 } else if (keypress
.equals(kbEnd
)) {
722 // End - rightmost column
723 selectedColumn
= columns
.size() - 1;
724 activate(columns
.get(selectedColumn
).get(selectedRow
));
725 } else if (keypress
.equals(kbPgUp
)) {
726 // PgUp - Treat like multiple up
727 for (int i
= 0; i
< getHeight() - 2; i
++) {
728 if (selectedRow
> 0) {
732 activate(columns
.get(selectedColumn
).get(selectedRow
));
733 } else if (keypress
.equals(kbPgDn
)) {
734 // PgDn - Treat like multiple up
735 for (int i
= 0; i
< getHeight() - 2; i
++) {
736 if (selectedRow
< rows
.size() - 1) {
740 activate(columns
.get(selectedColumn
).get(selectedRow
));
741 } else if (keypress
.equals(kbCtrlHome
)) {
742 // Ctrl-Home - go to top-left
745 activate(columns
.get(selectedColumn
).get(selectedRow
));
746 activate(columns
.get(selectedColumn
).get(selectedRow
));
747 } else if (keypress
.equals(kbCtrlEnd
)) {
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
));
755 super.onKeypress(keypress
);
758 // We may have scrolled off screen. Reset positions as needed to
759 // make the newly selected cell visible.
764 * Handle widget resize events.
766 * @param event resize event
769 public void onResize(final TResizeEvent event
) {
770 super.onResize(event
);
776 * Handle posted menu events.
778 * @param menu menu event
781 public void onMenu(final TMenuEvent menu
) {
782 switch (menu
.getId()) {
783 case TMenu
.MID_TABLE_VIEW_ROW_LABELS
:
784 showRowLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
786 case TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
:
787 showColumnLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
789 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
:
790 highlightRow
= getApplication().getMenuItem(menu
.getId()).getChecked();
792 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
:
793 highlightColumn
= getApplication().getMenuItem(menu
.getId()).getChecked();
795 case TMenu
.MID_TABLE_BORDER_NONE
:
796 if (selectedRow
== 0) {
797 topBorder
= Border
.NONE
;
799 if (selectedColumn
== 0) {
800 leftBorder
= Border
.NONE
;
802 columns
.get(selectedColumn
).rightBorder
= Border
.NONE
;
803 rows
.get(selectedRow
).bottomBorder
= Border
.NONE
;
804 rows
.get(selectedRow
).height
= 1;
806 case TMenu
.MID_TABLE_BORDER_ALL
:
807 if (selectedRow
== 0) {
808 topBorder
= Border
.SINGLE
;
810 if (selectedColumn
== 0) {
811 leftBorder
= Border
.SINGLE
;
813 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
814 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
815 rows
.get(selectedRow
).height
= 2;
817 case TMenu
.MID_TABLE_BORDER_RIGHT
:
818 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
820 case TMenu
.MID_TABLE_BORDER_LEFT
:
821 if (selectedColumn
== 0) {
822 leftBorder
= Border
.SINGLE
;
824 columns
.get(selectedColumn
- 1).rightBorder
= Border
.SINGLE
;
827 case TMenu
.MID_TABLE_BORDER_TOP
:
828 if (selectedRow
== 0) {
829 topBorder
= Border
.SINGLE
;
831 rows
.get(selectedRow
- 1).bottomBorder
= Border
.SINGLE
;
832 rows
.get(selectedRow
- 1).height
= 2;
835 case TMenu
.MID_TABLE_BORDER_BOTTOM
:
836 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
837 rows
.get(selectedRow
).height
= 2;
839 case TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
:
840 rows
.get(selectedRow
).bottomBorder
= Border
.DOUBLE
;
841 rows
.get(selectedRow
).height
= 2;
843 case TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
:
844 rows
.get(selectedRow
).bottomBorder
= Border
.THICK
;
845 rows
.get(selectedRow
).height
= 2;
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
:
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
);
860 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
862 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
863 columns
.get(i
).setX(columns
.get(i
).getX() - 1);
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
);
870 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
872 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
873 columns
.get(i
).setX(columns
.get(i
).getX() + 1);
876 case TMenu
.MID_TABLE_FILE_SAVE_CSV
:
879 case TMenu
.MID_TABLE_FILE_SAVE_TEXT
:
886 // Fix/redraw the display.
890 // ------------------------------------------------------------------------
891 // TWidget ----------------------------------------------------------------
892 // ------------------------------------------------------------------------
895 * Draw the table row/column labels, and borders.
899 CellAttributes labelColor
= getTheme().getColor("ttable.label");
900 CellAttributes labelColorSelected
= getTheme().getColor("ttable.label.selected");
901 CellAttributes borderColor
= getTheme().getColor("ttable.border");
904 if (showColumnLabels
== true) {
905 for (int i
= left
; i
< columns
.size(); i
++) {
906 if (columns
.get(i
).get(top
).isVisible() == false) {
909 putStringXY(columns
.get(i
).get(top
).getX(),
910 (topBorder
== Border
.NONE ?
0 : 1),
911 String
.format(" %-" +
912 (columns
.get(i
).width
- 2)
913 + "s ", columns
.get(i
).label
),
914 (i
== selectedColumn ? labelColorSelected
: labelColor
));
919 if (showRowLabels
== true) {
920 for (int i
= top
; i
< rows
.size(); i
++) {
921 if (rows
.get(i
).get(left
).isVisible() == false) {
924 putStringXY(0, rows
.get(i
).get(left
).getY(),
925 String
.format(" %-6s ", rows
.get(i
).label
),
926 (i
== selectedRow ? labelColorSelected
: labelColor
));
930 // Draw vertical borders.
931 if (leftBorder
== Border
.SINGLE
) {
932 vLineXY((showRowLabels ? ROW_LABEL_WIDTH
: 0), 0,
933 getHeight(), '\u2502', borderColor
);
935 for (int i
= left
; i
< columns
.size(); i
++) {
936 if (columns
.get(i
).get(top
).isVisible() == false) {
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
);
946 // Draw horizontal borders.
947 if (topBorder
== Border
.SINGLE
) {
948 hLineXY((showRowLabels ? ROW_LABEL_WIDTH
: 0), 0,
949 getWidth(), '\u2500', borderColor
);
951 for (int i
= top
; i
< rows
.size(); i
++) {
952 if (rows
.get(i
).get(left
).isVisible() == false) {
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
);
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
);
978 // TODO: draw the correct corners between rows and columns
980 // Now draw the window borders.
984 // ------------------------------------------------------------------------
985 // TTable -----------------------------------------------------------------
986 // ------------------------------------------------------------------------
989 * Get the currently-selected cell.
991 * @return the selected cell
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
));
1001 return (columns
.get(selectedColumn
).get(selectedRow
));
1005 * Get the currently-selected column.
1007 * @return the selected column
1009 public Column
getSelectedColumn() {
1010 assert (selectedColumn
>= 0);
1011 assert (columns
.size() > selectedColumn
);
1012 assert (columns
.get(selectedColumn
) != null);
1013 return columns
.get(selectedColumn
);
1017 * Get the currently-selected row.
1019 * @return the selected row
1021 public Row
getSelectedRow() {
1022 assert (selectedRow
>= 0);
1023 assert (rows
.size() > selectedRow
);
1024 assert (rows
.get(selectedRow
) != null);
1025 return rows
.get(selectedRow
);
1029 * Get the currently-selected column number. 0 is the left-most column.
1031 * @return the selected column number
1033 public int getSelectedColumnNumber() {
1034 return selectedColumn
;
1038 * Set the currently-selected column number. 0 is the left-most column.
1040 * @param column the column number to select
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
);
1047 selectedColumn
= column
;
1048 activate(columns
.get(selectedColumn
).get(selectedRow
));
1053 * Get the currently-selected row number. 0 is the top-most row.
1055 * @return the selected row number
1057 public int getSelectedRowNumber() {
1062 * Set the currently-selected row number. 0 is the left-most column.
1064 * @param row the row number to select
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
);
1072 activate(columns
.get(selectedColumn
).get(selectedRow
));
1077 * Get the number of columns.
1079 * @return the number of columns
1081 public int getColumnCount() {
1082 return columns
.size();
1086 * Get the number of rows.
1088 * @return the number of rows
1090 public int getRowCount() {
1095 * Align the grid so that the selected cell is fully visible.
1097 private void alignGrid() {
1098 int viewColumns
= getWidth();
1099 if (showRowLabels
== true) {
1100 viewColumns
-= ROW_LABEL_WIDTH
;
1102 if (leftBorder
!= Border
.NONE
) {
1105 int viewRows
= getHeight();
1106 if (showColumnLabels
== true) {
1107 viewRows
-= COLUMN_LABEL_HEIGHT
;
1109 if (topBorder
!= Border
.NONE
) {
1113 // If we pushed left or right, adjust the box to include the new
1115 if (selectedColumn
< left
) {
1116 left
= selectedColumn
- 1;
1121 if (selectedRow
< top
) {
1122 top
= selectedRow
- 1;
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)
1135 * We need to calculate right and bottom now.
1139 boolean done
= false;
1141 int rightCellX
= (showRowLabels ? ROW_LABEL_WIDTH
: 0);
1142 if (leftBorder
!= Border
.NONE
) {
1145 int maxCellX
= rightCellX
+ viewColumns
;
1147 boolean selectedIsVisible
= false;
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;
1156 rightCellX
+= columns
.get(x
).width
+ 1;
1157 if (rightCellX
>= maxCellX
) {
1162 if (right
< selectedColumn
) {
1163 // selectedColumn is outside the view range. Push left over,
1164 // and calculate again.
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.
1170 } else if (selectedIsVisible
== false) {
1171 // selectedColumn doesn't fit inside the view range, continue
1175 // selectedColumn is fully visible, all done.
1176 assert (selectedIsVisible
== true);
1182 // We have the left/right range correct, set cell visibility and
1183 // column X positions.
1184 int leftCellX
= showRowLabels ? ROW_LABEL_WIDTH
: 0;
1185 if (leftBorder
!= Border
.NONE
) {
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);
1196 for (int i
= 0; i
< rows
.size(); i
++) {
1197 columns
.get(x
).get(i
).setVisible(true);
1199 columns
.get(x
).setX(leftCellX
);
1200 leftCellX
+= columns
.get(x
).width
+ 1;
1207 int bottomCellY
= (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0);
1208 if (topBorder
!= Border
.NONE
) {
1211 int maxCellY
= bottomCellY
+ viewRows
;
1213 for (int y
= top
; y
< rows
.size(); y
++) {
1214 bottomCellY
+= rows
.get(y
).height
;
1215 if (bottomCellY
>= maxCellY
) {
1220 if (bottom
< selectedRow
) {
1221 // selectedRow is outside the view range. Push top down, and
1225 // selectedRow is inside the view range, done.
1230 // We have the top/bottom range correct, set cell visibility and
1232 int topCellY
= showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0;
1233 if (topBorder
!= Border
.NONE
) {
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);
1241 rows
.get(y
).setY(getHeight() + 1);
1244 for (int i
= 0; i
< columns
.size(); i
++) {
1245 rows
.get(y
).get(i
).setVisible(true);
1247 rows
.get(y
).setY(topCellY
);
1248 topCellY
+= rows
.get(y
).height
;
1254 * Save contents to file.
1256 * @param filename file to save to
1257 * @throws IOException if a java.io operation throws
1259 public void saveToFilename(final String filename
) throws IOException
{