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: \u2503 (vertical heavy) and \u2501 (horizontal heavy).
85 private static final int ROW_LABEL_WIDTH
= 8;
88 * Column label height.
90 private static final int COLUMN_LABEL_HEIGHT
= 1;
93 * Column default width.
95 private static final int COLUMN_DEFAULT_WIDTH
= 8;
100 private static final int EXTRA_ROWS
= 10;
103 * Extra columns to add.
105 private static final int EXTRA_COLUMNS
= 10 * (8 + 1);
107 // ------------------------------------------------------------------------
108 // Variables --------------------------------------------------------------
109 // ------------------------------------------------------------------------
112 * The underlying data, organized as columns.
114 private ArrayList
<Column
> columns
= new ArrayList
<Column
>();
117 * The underlying data, organized as rows.
119 private ArrayList
<Row
> rows
= new ArrayList
<Row
>();
122 * The row in model corresponding to the top-left visible cell.
127 * The column in model corresponding to the top-left visible cell.
129 private int left
= 0;
132 * The row in model corresponding to the currently selected cell.
134 private int selectedRow
= 0;
137 * The column in model corresponding to the currently selected cell.
139 private int selectedColumn
= 0;
142 * If true, highlight the entire row of the currently-selected cell.
144 private boolean highlightRow
= false;
147 * If true, highlight the entire column of the currently-selected cell.
149 private boolean highlightColumn
= false;
152 * If true, show the row labels as the first column.
154 private boolean showRowLabels
= true;
157 * If true, show the column labels as the first row.
159 private boolean showColumnLabels
= true;
162 * The top border for the first row.
164 private Border topBorder
= Border
.NONE
;
167 * The left border for the first column.
169 private Border leftBorder
= Border
.NONE
;
172 * Column represents a column of cells.
174 public class Column
{
177 * X position of this column.
184 private int width
= COLUMN_DEFAULT_WIDTH
;
187 * The cells of this column.
189 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
194 private String label
= "";
197 * The right border for this column.
199 private Border rightBorder
= Border
.NONE
;
202 * Constructor sets label to lettered column.
204 * @param col column number to use for this column. Column 0 will be
205 * "A", column 1 will be "B", column 26 will be "AA", and so on.
208 StringBuilder sb
= new StringBuilder();
210 sb
.append((char) ('A' + (col
% 26)));
216 label
= sb
.reverse().toString();
220 * Add an entry to this column.
222 * @param cell the cell to add
224 public void add(final Cell cell
) {
229 * Get an entry from this column.
231 * @param row the entry index to get
232 * @return the cell at row
234 public Cell
get(final int row
) {
235 return cells
.get(row
);
239 * Get the X position of the cells in this column.
241 * @return the position
248 * Set the X position of the cells in this column.
250 * @param x the position
252 public void setX(final int x
) {
253 for (Cell cell
: cells
) {
262 * Row represents a row of cells.
267 * Y position of this row.
274 private int height
= 1;
277 * The cells of this row.
279 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
284 private String label
= "";
287 * The bottom border for this row.
289 private Border bottomBorder
= Border
.NONE
;
292 * Constructor sets label to numbered row.
294 * @param row row number to use for this row
297 label
= Integer
.toString(row
);
301 * Add an entry to this column.
303 * @param cell the cell to add
305 public void add(final Cell cell
) {
310 * Get an entry from this row.
312 * @param column the entry index to get
313 * @return the cell at column
315 public Cell
get(final int column
) {
316 return cells
.get(column
);
319 * Get the Y position of the cells in this column.
321 * @return the position
328 * Set the Y position of the cells in this column.
330 * @param y the position
332 public void setY(final int y
) {
333 for (Cell cell
: cells
) {
342 * Cell represents an editable cell in the table. Normally, navigation
343 * to a cell only highlights it; pressing Enter or F2 will switch to
346 public class Cell
extends TWidget
{
348 // --------------------------------------------------------------------
349 // Variables ----------------------------------------------------------
350 // --------------------------------------------------------------------
353 * The field containing the cell's data.
355 private TField field
;
358 * The column of this cell.
363 * The row of this cell.
368 * If true, the cell is being edited.
370 private boolean isEditing
= false;
373 * Text of field before editing.
375 private String fieldText
;
377 // --------------------------------------------------------------------
378 // Constructors -------------------------------------------------------
379 // --------------------------------------------------------------------
382 * Public constructor.
384 * @param parent parent widget
385 * @param x column relative to parent
386 * @param y row relative to parent
387 * @param width width of widget
388 * @param height height of widget
389 * @param column column index of this cell
390 * @param row row index of this cell
392 public Cell(final TTableWidget parent
, final int x
, final int y
,
393 final int width
, final int height
, final int column
,
396 super(parent
, x
, y
, width
, height
);
397 this.column
= column
;
400 field
= addField(0, 0, width
, false);
401 field
.setEnabled(false);
402 field
.setBackgroundChar(' ');
405 // --------------------------------------------------------------------
406 // Event handlers -----------------------------------------------------
407 // --------------------------------------------------------------------
410 * Handle mouse double-click events.
412 * @param mouse mouse double-click event
415 public void onMouseDoubleClick(final TMouseEvent mouse
) {
416 // Use TWidget's code to pass the event to the children.
417 super.onMouseDown(mouse
);
419 // Double-click means to start editing.
420 fieldText
= field
.getText();
422 field
.setEnabled(true);
426 // Let the table know that I was activated.
427 ((TTableWidget
) getParent()).selectedRow
= row
;
428 ((TTableWidget
) getParent()).selectedColumn
= column
;
429 ((TTableWidget
) getParent()).alignGrid();
434 * Handle mouse press events.
436 * @param mouse mouse button press event
439 public void onMouseDown(final TMouseEvent mouse
) {
440 // Use TWidget's code to pass the event to the children.
441 super.onMouseDown(mouse
);
444 // Let the table know that I was activated.
445 ((TTableWidget
) getParent()).selectedRow
= row
;
446 ((TTableWidget
) getParent()).selectedColumn
= column
;
447 ((TTableWidget
) getParent()).alignGrid();
452 * Handle mouse release events.
454 * @param mouse mouse button release event
457 public void onMouseUp(final TMouseEvent mouse
) {
458 // Use TWidget's code to pass the event to the children.
459 super.onMouseDown(mouse
);
462 // Let the table know that I was activated.
463 ((TTableWidget
) getParent()).selectedRow
= row
;
464 ((TTableWidget
) getParent()).selectedColumn
= column
;
465 ((TTableWidget
) getParent()).alignGrid();
472 * @param keypress keystroke event
475 public void onKeypress(final TKeypressEvent keypress
) {
476 // System.err.println("Cell onKeypress: " + keypress);
479 if (keypress
.equals(kbEsc
)) {
480 // ESC cancels the edit.
481 field
.setText(fieldText
);
483 field
.setEnabled(false);
486 if (keypress
.equals(kbEnter
)) {
487 // Enter ends editing.
488 fieldText
= field
.getText();
490 field
.setEnabled(false);
493 // Pass down to field.
494 super.onKeypress(keypress
);
497 if (keypress
.equals(kbEnter
) || keypress
.equals(kbF2
)) {
498 // Enter or F2 starts editing.
499 fieldText
= field
.getText();
501 field
.setEnabled(true);
507 // --------------------------------------------------------------------
508 // TWidget ------------------------------------------------------------
509 // --------------------------------------------------------------------
516 TTableWidget table
= (TTableWidget
) getParent();
518 if (isAbsoluteActive()) {
520 field
.setActiveColorKey("tfield.active");
521 field
.setInactiveColorKey("tfield.inactive");
523 field
.setActiveColorKey("ttable.selected");
524 field
.setInactiveColorKey("ttable.selected");
526 } else if (((table
.selectedColumn
== column
)
527 && ((table
.selectedRow
== row
)
528 || (table
.highlightColumn
== true)))
529 || ((table
.selectedRow
== row
)
530 && ((table
.selectedColumn
== column
)
531 || (table
.highlightRow
== true)))
533 field
.setActiveColorKey("ttable.active");
534 field
.setInactiveColorKey("ttable.active");
536 field
.setActiveColorKey("ttable.active");
537 field
.setInactiveColorKey("ttable.inactive");
540 assert (isVisible() == true);
545 // --------------------------------------------------------------------
546 // TTable.Cell --------------------------------------------------------
547 // --------------------------------------------------------------------
554 public final String
getText() {
555 return field
.getText();
561 * @param text the new field text
563 public void setText(final String text
) {
569 // ------------------------------------------------------------------------
570 // Constructors -----------------------------------------------------------
571 // ------------------------------------------------------------------------
574 * Public constructor.
576 * @param parent parent widget
577 * @param x column relative to parent
578 * @param y row relative to parent
579 * @param width width of widget
580 * @param height height of widget
582 public TTableWidget(final TWidget parent
, final int x
, final int y
,
583 final int width
, final int height
) {
585 super(parent
, x
, y
, width
, height
);
587 // Initialize the starting row and column.
588 rows
.add(new Row(0));
589 columns
.add(new Column(0));
591 // Place a grid of cells that fit in this space.
593 for (int i
= 0; i
< height
+ EXTRA_ROWS
; i
+= rows
.get(0).height
) {
595 for (int j
= 0; j
< width
+ EXTRA_COLUMNS
;
596 j
+= columns
.get(0).width
) {
598 Cell cell
= new Cell(this, 0, 0, /* j, i, */ columns
.get(0).width
,
599 rows
.get(0).height
, column
, row
);
601 // DEBUG: set a grid of cell index labels
603 cell
.setText("" + row
+ " " + column
);
604 rows
.get(row
).add(cell
);
605 columns
.get(column
).add(cell
);
607 (j
+ columns
.get(0).width
< width
+ EXTRA_COLUMNS
)
609 columns
.add(new Column(column
+ 1));
613 if (i
+ rows
.get(0).height
< height
+ EXTRA_ROWS
) {
614 rows
.add(new Row(row
+ 1));
618 for (int i
= 0; i
< rows
.size(); i
++) {
619 rows
.get(i
).setY(i
+ (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0));
621 for (int j
= 0; j
< columns
.size(); j
++) {
622 columns
.get(j
).setX((j
* COLUMN_DEFAULT_WIDTH
) +
623 (showRowLabels ? ROW_LABEL_WIDTH
: 0));
625 activate(columns
.get(selectedColumn
).get(selectedRow
));
629 // Set the menu to match the flags.
630 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
).
631 setChecked(showRowLabels
);
632 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
).
633 setChecked(showColumnLabels
);
634 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
).
635 setChecked(highlightRow
);
636 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
).
637 setChecked(highlightColumn
);
642 // ------------------------------------------------------------------------
643 // Event handlers ---------------------------------------------------------
644 // ------------------------------------------------------------------------
647 * Handle mouse press events.
649 * @param mouse mouse button press event
652 public void onMouseDown(final TMouseEvent mouse
) {
653 if (mouse
.isMouseWheelUp() || mouse
.isMouseWheelDown()) {
654 // Treat wheel up/down as 3 up/down
655 TKeypressEvent keyEvent
;
656 if (mouse
.isMouseWheelUp()) {
657 keyEvent
= new TKeypressEvent(kbUp
);
659 keyEvent
= new TKeypressEvent(kbDown
);
661 for (int i
= 0; i
< 3; i
++) {
662 onKeypress(keyEvent
);
667 // Use TWidget's code to pass the event to the children.
668 super.onMouseDown(mouse
);
674 * @param keypress keystroke event
677 public void onKeypress(final TKeypressEvent keypress
) {
678 if (keypress
.equals(kbTab
)
679 || keypress
.equals(kbShiftTab
)
681 // Squash tab and back-tab. They don't make sense in the TTable
686 // If editing, pass to that cell and do nothing else.
687 if (getSelectedCell().isEditing
) {
688 super.onKeypress(keypress
);
692 if (keypress
.equals(kbLeft
)) {
694 if (selectedColumn
> 0) {
697 activate(columns
.get(selectedColumn
).get(selectedRow
));
698 } else if (keypress
.equals(kbRight
)) {
700 if (selectedColumn
< columns
.size() - 1) {
703 activate(columns
.get(selectedColumn
).get(selectedRow
));
704 } else if (keypress
.equals(kbUp
)) {
706 if (selectedRow
> 0) {
709 activate(columns
.get(selectedColumn
).get(selectedRow
));
710 } else if (keypress
.equals(kbDown
)) {
712 if (selectedRow
< rows
.size() - 1) {
715 activate(columns
.get(selectedColumn
).get(selectedRow
));
716 } else if (keypress
.equals(kbHome
)) {
717 // Home - leftmost column
719 activate(columns
.get(selectedColumn
).get(selectedRow
));
720 } else if (keypress
.equals(kbEnd
)) {
721 // End - rightmost column
722 selectedColumn
= columns
.size() - 1;
723 activate(columns
.get(selectedColumn
).get(selectedRow
));
724 } else if (keypress
.equals(kbPgUp
)) {
725 // PgUp - Treat like multiple up
726 for (int i
= 0; i
< getHeight() - 2; i
++) {
727 if (selectedRow
> 0) {
731 activate(columns
.get(selectedColumn
).get(selectedRow
));
732 } else if (keypress
.equals(kbPgDn
)) {
733 // PgDn - Treat like multiple up
734 for (int i
= 0; i
< getHeight() - 2; i
++) {
735 if (selectedRow
< rows
.size() - 1) {
739 activate(columns
.get(selectedColumn
).get(selectedRow
));
740 } else if (keypress
.equals(kbCtrlHome
)) {
741 // Ctrl-Home - go to top-left
744 activate(columns
.get(selectedColumn
).get(selectedRow
));
745 activate(columns
.get(selectedColumn
).get(selectedRow
));
746 } else if (keypress
.equals(kbCtrlEnd
)) {
747 // Ctrl-End - go to bottom-right
748 selectedRow
= rows
.size() - 1;
749 selectedColumn
= columns
.size() - 1;
750 activate(columns
.get(selectedColumn
).get(selectedRow
));
751 activate(columns
.get(selectedColumn
).get(selectedRow
));
754 super.onKeypress(keypress
);
757 // We may have scrolled off screen. Reset positions as needed to
758 // make the newly selected cell visible.
763 * Handle widget resize events.
765 * @param event resize event
768 public void onResize(final TResizeEvent event
) {
769 super.onResize(event
);
775 * Handle posted menu events.
777 * @param menu menu event
780 public void onMenu(final TMenuEvent menu
) {
781 switch (menu
.getId()) {
782 case TMenu
.MID_TABLE_VIEW_ROW_LABELS
:
783 showRowLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
785 case TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
:
786 showColumnLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
788 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
:
789 highlightRow
= getApplication().getMenuItem(menu
.getId()).getChecked();
791 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
:
792 highlightColumn
= getApplication().getMenuItem(menu
.getId()).getChecked();
794 case TMenu
.MID_TABLE_BORDER_NONE
:
795 topBorder
= Border
.NONE
;
796 leftBorder
= Border
.NONE
;
797 for (int i
= 0; i
< columns
.size(); i
++) {
798 columns
.get(i
).rightBorder
= Border
.NONE
;
800 for (int i
= 0; i
< rows
.size(); i
++) {
801 rows
.get(i
).bottomBorder
= Border
.NONE
;
802 rows
.get(i
).height
= 1;
805 case TMenu
.MID_TABLE_BORDER_ALL
:
806 topBorder
= Border
.SINGLE
;
807 leftBorder
= Border
.SINGLE
;
808 for (int i
= 0; i
< columns
.size(); i
++) {
809 columns
.get(i
).rightBorder
= Border
.SINGLE
;
811 for (int i
= 0; i
< rows
.size(); i
++) {
812 rows
.get(i
).bottomBorder
= Border
.SINGLE
;
813 rows
.get(i
).height
= 2;
816 case TMenu
.MID_TABLE_BORDER_CELL_NONE
:
817 if (selectedRow
== 0) {
818 topBorder
= Border
.NONE
;
820 if (selectedColumn
== 0) {
821 leftBorder
= Border
.NONE
;
823 columns
.get(selectedColumn
).rightBorder
= Border
.NONE
;
824 rows
.get(selectedRow
).bottomBorder
= Border
.NONE
;
825 rows
.get(selectedRow
).height
= 1;
827 case TMenu
.MID_TABLE_BORDER_CELL_ALL
:
828 if (selectedRow
== 0) {
829 topBorder
= Border
.SINGLE
;
831 if (selectedColumn
== 0) {
832 leftBorder
= Border
.SINGLE
;
834 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
835 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
836 rows
.get(selectedRow
).height
= 2;
838 case TMenu
.MID_TABLE_BORDER_RIGHT
:
839 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
841 case TMenu
.MID_TABLE_BORDER_LEFT
:
842 if (selectedColumn
== 0) {
843 leftBorder
= Border
.SINGLE
;
845 columns
.get(selectedColumn
- 1).rightBorder
= Border
.SINGLE
;
848 case TMenu
.MID_TABLE_BORDER_TOP
:
849 if (selectedRow
== 0) {
850 topBorder
= Border
.SINGLE
;
852 rows
.get(selectedRow
- 1).bottomBorder
= Border
.SINGLE
;
853 rows
.get(selectedRow
- 1).height
= 2;
856 case TMenu
.MID_TABLE_BORDER_BOTTOM
:
857 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
858 rows
.get(selectedRow
).height
= 2;
860 case TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
:
861 rows
.get(selectedRow
).bottomBorder
= Border
.DOUBLE
;
862 rows
.get(selectedRow
).height
= 2;
864 case TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
:
865 rows
.get(selectedRow
).bottomBorder
= Border
.THICK
;
866 rows
.get(selectedRow
).height
= 2;
868 case TMenu
.MID_TABLE_DELETE_LEFT
:
869 case TMenu
.MID_TABLE_DELETE_UP
:
870 case TMenu
.MID_TABLE_DELETE_ROW
:
871 case TMenu
.MID_TABLE_DELETE_COLUMN
:
872 case TMenu
.MID_TABLE_INSERT_LEFT
:
873 case TMenu
.MID_TABLE_INSERT_RIGHT
:
874 case TMenu
.MID_TABLE_INSERT_ABOVE
:
875 case TMenu
.MID_TABLE_INSERT_BELOW
:
877 case TMenu
.MID_TABLE_COLUMN_NARROW
:
878 columns
.get(selectedColumn
).width
--;
879 for (Cell cell
: getSelectedColumn().cells
) {
880 cell
.setWidth(columns
.get(selectedColumn
).width
);
881 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
883 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
884 columns
.get(i
).setX(columns
.get(i
).getX() - 1);
887 case TMenu
.MID_TABLE_COLUMN_WIDEN
:
888 columns
.get(selectedColumn
).width
++;
889 for (Cell cell
: getSelectedColumn().cells
) {
890 cell
.setWidth(columns
.get(selectedColumn
).width
);
891 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
893 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
894 columns
.get(i
).setX(columns
.get(i
).getX() + 1);
897 case TMenu
.MID_TABLE_FILE_SAVE_CSV
:
900 case TMenu
.MID_TABLE_FILE_SAVE_TEXT
:
907 // Fix/redraw the display.
911 // ------------------------------------------------------------------------
912 // TWidget ----------------------------------------------------------------
913 // ------------------------------------------------------------------------
916 * Draw the table row/column labels, and borders.
920 CellAttributes labelColor
= getTheme().getColor("ttable.label");
921 CellAttributes labelColorSelected
= getTheme().getColor("ttable.label.selected");
922 CellAttributes borderColor
= getTheme().getColor("ttable.border");
925 if (showColumnLabels
== true) {
926 for (int i
= left
; i
< columns
.size(); i
++) {
927 if (columns
.get(i
).get(top
).isVisible() == false) {
930 putStringXY(columns
.get(i
).get(top
).getX(), 0,
931 String
.format(" %-" +
932 (columns
.get(i
).width
- 2)
933 + "s ", columns
.get(i
).label
),
934 (i
== selectedColumn ? labelColorSelected
: labelColor
));
939 if (showRowLabels
== true) {
940 for (int i
= top
; i
< rows
.size(); i
++) {
941 if (rows
.get(i
).get(left
).isVisible() == false) {
944 putStringXY(0, rows
.get(i
).get(left
).getY(),
945 String
.format(" %-6s ", rows
.get(i
).label
),
946 (i
== selectedRow ? labelColorSelected
: labelColor
));
950 // Draw vertical borders.
951 if (leftBorder
== Border
.SINGLE
) {
952 vLineXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
953 (topBorder
== Border
.NONE ?
0 : 1) +
954 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
955 getHeight(), '\u2502', borderColor
);
957 for (int i
= left
; i
< columns
.size(); i
++) {
958 if (columns
.get(i
).get(top
).isVisible() == false) {
961 if (columns
.get(i
).rightBorder
== Border
.SINGLE
) {
962 vLineXY(columns
.get(i
).getX() + columns
.get(i
).width
,
963 (topBorder
== Border
.NONE ?
0 : 1) +
964 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
965 getHeight(), '\u2502', borderColor
);
969 // Draw horizontal borders.
970 if (topBorder
== Border
.SINGLE
) {
971 hLineXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
972 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
973 getWidth(), '\u2500', borderColor
);
975 for (int i
= top
; i
< rows
.size(); i
++) {
976 if (rows
.get(i
).get(left
).isVisible() == false) {
979 if (rows
.get(i
).bottomBorder
== Border
.SINGLE
) {
980 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
981 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
982 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
983 getWidth(), '\u2500', borderColor
);
984 } else if (rows
.get(i
).bottomBorder
== Border
.DOUBLE
) {
985 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
986 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
987 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
988 getWidth(), '\u2550', borderColor
);
989 } else if (rows
.get(i
).bottomBorder
== Border
.THICK
) {
990 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
991 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
992 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
993 getWidth(), '\u2501', borderColor
);
996 // Top-left corner if needed
997 if ((topBorder
== Border
.SINGLE
) && (leftBorder
== Border
.SINGLE
)) {
998 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
999 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
1000 '\u250c', borderColor
);
1003 // Now draw the correct corners
1004 for (int i
= top
; i
< rows
.size(); i
++) {
1005 if (rows
.get(i
).get(left
).isVisible() == false) {
1008 for (int j
= left
; j
< columns
.size(); j
++) {
1009 if (columns
.get(j
).get(i
).isVisible() == false) {
1012 if ((i
== top
) && (topBorder
== Border
.SINGLE
)
1013 && (columns
.get(j
).rightBorder
== Border
.SINGLE
)
1016 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
1017 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
1018 '\u252c', borderColor
);
1020 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
1021 && (rows
.get(i
).bottomBorder
== Border
.SINGLE
)
1024 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1025 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1026 '\u251c', borderColor
);
1028 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
1029 && (rows
.get(i
).bottomBorder
== Border
.SINGLE
)
1031 // Intersection of single bars
1032 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
1033 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1034 '\u253c', borderColor
);
1036 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
1037 && (rows
.get(i
).bottomBorder
== Border
.DOUBLE
)
1039 // Left tee: single bar vertical, double bar horizontal
1040 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1041 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1042 '\u255e', borderColor
);
1044 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
1045 && (rows
.get(i
).bottomBorder
== Border
.THICK
)
1047 // Left tee: single bar vertical, thick bar horizontal
1048 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1049 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1050 '\u251d', borderColor
);
1052 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
1053 && (rows
.get(i
).bottomBorder
== Border
.DOUBLE
)
1055 // Intersection: single bar vertical, double bar
1057 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
1058 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1059 '\u256a', borderColor
);
1061 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
1062 && (rows
.get(i
).bottomBorder
== Border
.THICK
)
1064 // Intersection: single bar vertical, thick bar
1066 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
1067 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1068 '\u253f', borderColor
);
1073 // Now draw the window borders.
1077 // ------------------------------------------------------------------------
1078 // TTable -----------------------------------------------------------------
1079 // ------------------------------------------------------------------------
1082 * Get the currently-selected cell.
1084 * @return the selected cell
1086 public Cell
getSelectedCell() {
1087 assert (rows
.get(selectedRow
) != null);
1088 assert (rows
.get(selectedRow
).get(selectedColumn
) != null);
1089 assert (columns
.get(selectedColumn
) != null);
1090 assert (columns
.get(selectedColumn
).get(selectedRow
) != null);
1091 assert (rows
.get(selectedRow
).get(selectedColumn
) ==
1092 columns
.get(selectedColumn
).get(selectedRow
));
1094 return (columns
.get(selectedColumn
).get(selectedRow
));
1098 * Get the currently-selected column.
1100 * @return the selected column
1102 public Column
getSelectedColumn() {
1103 assert (selectedColumn
>= 0);
1104 assert (columns
.size() > selectedColumn
);
1105 assert (columns
.get(selectedColumn
) != null);
1106 return columns
.get(selectedColumn
);
1110 * Get the currently-selected row.
1112 * @return the selected row
1114 public Row
getSelectedRow() {
1115 assert (selectedRow
>= 0);
1116 assert (rows
.size() > selectedRow
);
1117 assert (rows
.get(selectedRow
) != null);
1118 return rows
.get(selectedRow
);
1122 * Get the currently-selected column number. 0 is the left-most column.
1124 * @return the selected column number
1126 public int getSelectedColumnNumber() {
1127 return selectedColumn
;
1131 * Set the currently-selected column number. 0 is the left-most column.
1133 * @param column the column number to select
1135 public void setSelectedColumnNumber(final int column
) {
1136 if ((column
< 0) || (column
> columns
.size() - 1)) {
1137 throw new IndexOutOfBoundsException("Column count is " +
1138 columns
.size() + ", requested index " + column
);
1140 selectedColumn
= column
;
1141 activate(columns
.get(selectedColumn
).get(selectedRow
));
1146 * Get the currently-selected row number. 0 is the top-most row.
1148 * @return the selected row number
1150 public int getSelectedRowNumber() {
1155 * Set the currently-selected row number. 0 is the left-most column.
1157 * @param row the row number to select
1159 public void setSelectedRowNumber(final int row
) {
1160 if ((row
< 0) || (row
> rows
.size() - 1)) {
1161 throw new IndexOutOfBoundsException("Row count is " +
1162 rows
.size() + ", requested index " + row
);
1165 activate(columns
.get(selectedColumn
).get(selectedRow
));
1170 * Get the number of columns.
1172 * @return the number of columns
1174 public int getColumnCount() {
1175 return columns
.size();
1179 * Get the number of rows.
1181 * @return the number of rows
1183 public int getRowCount() {
1188 * Align the grid so that the selected cell is fully visible.
1190 private void alignGrid() {
1191 int viewColumns
= getWidth();
1192 if (showRowLabels
== true) {
1193 viewColumns
-= ROW_LABEL_WIDTH
;
1195 if (leftBorder
!= Border
.NONE
) {
1198 int viewRows
= getHeight();
1199 if (showColumnLabels
== true) {
1200 viewRows
-= COLUMN_LABEL_HEIGHT
;
1202 if (topBorder
!= Border
.NONE
) {
1206 // If we pushed left or right, adjust the box to include the new
1208 if (selectedColumn
< left
) {
1209 left
= selectedColumn
- 1;
1214 if (selectedRow
< top
) {
1215 top
= selectedRow
- 1;
1222 * viewColumns and viewRows now contain the available columns and
1223 * rows available to view the selected cell. We adjust left and top
1224 * to ensure the selected cell is within view, and then make all
1225 * cells outside the box between (left, top) and (right, bottom)
1228 * We need to calculate right and bottom now.
1232 boolean done
= false;
1234 int rightCellX
= (showRowLabels ? ROW_LABEL_WIDTH
: 0);
1235 if (leftBorder
!= Border
.NONE
) {
1238 int maxCellX
= rightCellX
+ viewColumns
;
1240 boolean selectedIsVisible
= false;
1242 for (int x
= left
; x
< columns
.size(); x
++) {
1243 if (x
== selectedColumn
) {
1244 selectedX
= rightCellX
;
1245 if (selectedX
+ columns
.get(x
).width
+ 1 <= maxCellX
) {
1246 selectedIsVisible
= true;
1249 rightCellX
+= columns
.get(x
).width
+ 1;
1250 if (rightCellX
>= maxCellX
) {
1255 if (right
< selectedColumn
) {
1256 // selectedColumn is outside the view range. Push left over,
1257 // and calculate again.
1259 } else if (left
== selectedColumn
) {
1260 // selectedColumn doesn't fit inside the view range, but we
1261 // can't go over any further either. Bail out.
1263 } else if (selectedIsVisible
== false) {
1264 // selectedColumn doesn't fit inside the view range, continue
1268 // selectedColumn is fully visible, all done.
1269 assert (selectedIsVisible
== true);
1275 // We have the left/right range correct, set cell visibility and
1276 // column X positions.
1277 int leftCellX
= showRowLabels ? ROW_LABEL_WIDTH
: 0;
1278 if (leftBorder
!= Border
.NONE
) {
1281 for (int x
= 0; x
< columns
.size(); x
++) {
1282 if ((x
< left
) || (x
> right
)) {
1283 for (int i
= 0; i
< rows
.size(); i
++) {
1284 columns
.get(x
).get(i
).setVisible(false);
1285 columns
.get(x
).setX(getWidth() + 1);
1289 for (int i
= 0; i
< rows
.size(); i
++) {
1290 columns
.get(x
).get(i
).setVisible(true);
1292 columns
.get(x
).setX(leftCellX
);
1293 leftCellX
+= columns
.get(x
).width
+ 1;
1300 int bottomCellY
= (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0);
1301 if (topBorder
!= Border
.NONE
) {
1304 int maxCellY
= bottomCellY
+ viewRows
;
1306 for (int y
= top
; y
< rows
.size(); y
++) {
1307 bottomCellY
+= rows
.get(y
).height
;
1308 if (bottomCellY
>= maxCellY
) {
1313 if (bottom
< selectedRow
) {
1314 // selectedRow is outside the view range. Push top down, and
1318 // selectedRow is inside the view range, done.
1323 // We have the top/bottom range correct, set cell visibility and
1325 int topCellY
= showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0;
1326 if (topBorder
!= Border
.NONE
) {
1329 for (int y
= 0; y
< rows
.size(); y
++) {
1330 if ((y
< top
) || (y
> bottom
)) {
1331 for (int i
= 0; i
< columns
.size(); i
++) {
1332 rows
.get(y
).get(i
).setVisible(false);
1334 rows
.get(y
).setY(getHeight() + 1);
1337 for (int i
= 0; i
< columns
.size(); i
++) {
1338 rows
.get(y
).get(i
).setVisible(true);
1340 rows
.get(y
).setY(topCellY
);
1341 topCellY
+= rows
.get(y
).height
;
1347 * Save contents to file.
1349 * @param filename file to save to
1350 * @throws IOException if a java.io operation throws
1352 public void saveToFilename(final String filename
) throws IOException
{