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
.BufferedReader
;
32 import java
.io
.BufferedWriter
;
34 import java
.io
.FileReader
;
35 import java
.io
.FileWriter
;
36 import java
.io
.IOException
;
37 import java
.util
.ArrayList
;
38 import java
.util
.List
;
40 import jexer
.bits
.CellAttributes
;
41 import jexer
.bits
.StringUtils
;
42 import jexer
.event
.TKeypressEvent
;
43 import jexer
.event
.TMouseEvent
;
44 import jexer
.event
.TResizeEvent
;
45 import static jexer
.TKeypress
.*;
48 * TTableWidget is used to display and edit regular two-dimensional tables of
51 * This class was inspired by a TTable implementation originally developed by
52 * David "Niki" ROULET [niki@nikiroo.be], made available under MIT at
53 * https://github.com/nikiroo/jexer/tree/ttable_pull.
55 public class TTableWidget
extends TWidget
{
57 // ------------------------------------------------------------------------
58 // Constants --------------------------------------------------------------
59 // ------------------------------------------------------------------------
62 * Available borders for cells.
71 * Single bar: \u2502 (vertical) and \u2500 (horizontal).
76 * Double bar: \u2551 (vertical) and \u2550 (horizontal).
81 * Thick bar: \u2503 (vertical heavy) and \u2501 (horizontal heavy).
87 * If true, put a grid of numbers in the cells.
89 private static final boolean DEBUG
= false;
94 private static final int ROW_LABEL_WIDTH
= 8;
97 * Column label height.
99 private static final int COLUMN_LABEL_HEIGHT
= 1;
102 * Column default width.
104 private static final int COLUMN_DEFAULT_WIDTH
= 8;
109 private static final int EXTRA_ROWS
= (DEBUG ?
10 : 0);
112 * Extra columns to add.
114 private static final int EXTRA_COLUMNS
= (DEBUG ?
3 : 0);
116 // ------------------------------------------------------------------------
117 // Variables --------------------------------------------------------------
118 // ------------------------------------------------------------------------
121 * The underlying data, organized as columns.
123 private ArrayList
<Column
> columns
= new ArrayList
<Column
>();
126 * The underlying data, organized as rows.
128 private ArrayList
<Row
> rows
= new ArrayList
<Row
>();
131 * The row in model corresponding to the top-left visible cell.
136 * The column in model corresponding to the top-left visible cell.
138 private int left
= 0;
141 * The row in model corresponding to the currently selected cell.
143 private int selectedRow
= 0;
146 * The column in model corresponding to the currently selected cell.
148 private int selectedColumn
= 0;
151 * If true, highlight the entire row of the currently-selected cell.
153 private boolean highlightRow
= false;
156 * If true, highlight the entire column of the currently-selected cell.
158 private boolean highlightColumn
= false;
161 * If true, show the row labels as the first column.
163 private boolean showRowLabels
= true;
166 * If true, show the column labels as the first row.
168 private boolean showColumnLabels
= true;
171 * The top border for the first row.
173 private Border topBorder
= Border
.NONE
;
176 * The left border for the first column.
178 private Border leftBorder
= Border
.NONE
;
181 * Column represents a column of cells.
183 public class Column
{
186 * X position of this column.
193 private int width
= COLUMN_DEFAULT_WIDTH
;
196 * The cells of this column.
198 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
203 private String label
= "";
206 * The right border for this column.
208 private Border rightBorder
= Border
.NONE
;
211 * Constructor sets label to lettered column.
213 * @param col column number to use for this column. Column 0 will be
214 * "A", column 1 will be "B", column 26 will be "AA", and so on.
217 label
= makeColumnLabel(col
);
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 * If true, the cell is read-only (non-editable).
376 private boolean readOnly
= false;
379 * Text of field before editing.
381 private String fieldText
;
383 // --------------------------------------------------------------------
384 // Constructors -------------------------------------------------------
385 // --------------------------------------------------------------------
388 * Public constructor.
390 * @param parent parent widget
391 * @param x column relative to parent
392 * @param y row relative to parent
393 * @param width width of widget
394 * @param height height of widget
395 * @param column column index of this cell
396 * @param row row index of this cell
398 public Cell(final TTableWidget parent
, final int x
, final int y
,
399 final int width
, final int height
, final int column
,
402 super(parent
, x
, y
, width
, height
);
403 this.column
= column
;
406 field
= addField(0, 0, width
, false);
407 field
.setEnabled(false);
408 field
.setBackgroundChar(' ');
411 // --------------------------------------------------------------------
412 // Event handlers -----------------------------------------------------
413 // --------------------------------------------------------------------
416 * Handle mouse double-click events.
418 * @param mouse mouse double-click event
421 public void onMouseDoubleClick(final TMouseEvent mouse
) {
422 // Use TWidget's code to pass the event to the children.
423 super.onMouseDown(mouse
);
425 // Double-click means to start editing.
426 fieldText
= field
.getText();
428 field
.setEnabled(true);
432 // Let the table know that I was activated.
433 ((TTableWidget
) getParent()).selectedRow
= row
;
434 ((TTableWidget
) getParent()).selectedColumn
= column
;
435 ((TTableWidget
) getParent()).alignGrid();
440 * Handle mouse press events.
442 * @param mouse mouse button press event
445 public void onMouseDown(final TMouseEvent mouse
) {
446 // Use TWidget's code to pass the event to the children.
447 super.onMouseDown(mouse
);
450 // Let the table know that I was activated.
451 ((TTableWidget
) getParent()).selectedRow
= row
;
452 ((TTableWidget
) getParent()).selectedColumn
= column
;
453 ((TTableWidget
) getParent()).alignGrid();
458 * Handle mouse release events.
460 * @param mouse mouse button release event
463 public void onMouseUp(final TMouseEvent mouse
) {
464 // Use TWidget's code to pass the event to the children.
465 super.onMouseDown(mouse
);
468 // Let the table know that I was activated.
469 ((TTableWidget
) getParent()).selectedRow
= row
;
470 ((TTableWidget
) getParent()).selectedColumn
= column
;
471 ((TTableWidget
) getParent()).alignGrid();
478 * @param keypress keystroke event
481 public void onKeypress(final TKeypressEvent keypress
) {
482 // System.err.println("Cell onKeypress: " + keypress);
485 // Read only: do nothing.
490 if (keypress
.equals(kbEsc
)) {
491 // ESC cancels the edit.
495 if (keypress
.equals(kbEnter
)) {
496 // Enter ends editing.
498 // Pass down to field first so that it can execute
499 // enterAction if specified.
500 super.onKeypress(keypress
);
502 fieldText
= field
.getText();
504 field
.setEnabled(false);
507 // Pass down to field.
508 super.onKeypress(keypress
);
511 if (keypress
.equals(kbEnter
) || keypress
.equals(kbF2
)) {
512 // Enter or F2 starts editing.
513 fieldText
= field
.getText();
515 field
.setEnabled(true);
521 // --------------------------------------------------------------------
522 // TWidget ------------------------------------------------------------
523 // --------------------------------------------------------------------
530 TTableWidget table
= (TTableWidget
) getParent();
532 if (isAbsoluteActive()) {
534 field
.setActiveColorKey("tfield.active");
535 field
.setInactiveColorKey("tfield.inactive");
537 field
.setActiveColorKey("ttable.selected");
538 field
.setInactiveColorKey("ttable.selected");
540 } else if (((table
.selectedColumn
== column
)
541 && ((table
.selectedRow
== row
)
542 || (table
.highlightColumn
== true)))
543 || ((table
.selectedRow
== row
)
544 && ((table
.selectedColumn
== column
)
545 || (table
.highlightRow
== true)))
547 field
.setActiveColorKey("ttable.active");
548 field
.setInactiveColorKey("ttable.active");
550 field
.setActiveColorKey("ttable.active");
551 field
.setInactiveColorKey("ttable.inactive");
554 assert (isVisible() == true);
559 // --------------------------------------------------------------------
560 // TTable.Cell --------------------------------------------------------
561 // --------------------------------------------------------------------
568 public final String
getText() {
569 return field
.getText();
575 * @param text the new field text
577 public void setText(final String text
) {
582 * Cancel any pending edit.
584 public void cancelEdit() {
585 // Cancel any pending edit.
586 if (fieldText
!= null) {
587 field
.setText(fieldText
);
590 field
.setEnabled(false);
594 * Set an entire column of cells read-only (non-editable) or not.
596 * @param readOnly if true, the cells will be non-editable
598 public void setReadOnly(final boolean readOnly
) {
600 this.readOnly
= readOnly
;
605 // ------------------------------------------------------------------------
606 // Constructors -----------------------------------------------------------
607 // ------------------------------------------------------------------------
610 * Public constructor.
612 * @param parent parent widget
613 * @param x column relative to parent
614 * @param y row relative to parent
615 * @param width width of widget
616 * @param height height of widget
617 * @param gridColumns number of columns in grid
618 * @param gridRows number of rows in grid
620 public TTableWidget(final TWidget parent
, final int x
, final int y
,
621 final int width
, final int height
, final int gridColumns
,
622 final int gridRows
) {
624 super(parent
, x
, y
, width
, height
);
627 System.err.println("gridColumns " + gridColumns +
628 " gridRows " + gridRows);
631 if (gridColumns
< 1) {
632 throw new IllegalArgumentException("Column count cannot be less " +
636 throw new IllegalArgumentException("Row count cannot be less " +
640 // Initialize the starting row and column.
641 rows
.add(new Row(0));
642 columns
.add(new Column(0));
643 assert (rows
.get(0).height
== 1);
645 // Place a grid of cells that fit in this space.
646 for (int row
= 0; row
< gridRows
; row
++) {
647 for (int column
= 0; column
< gridColumns
; column
++) {
648 Cell cell
= new Cell(this, 0, 0, COLUMN_DEFAULT_WIDTH
, 1,
652 // For debugging: set a grid of cell index labels.
653 cell
.setText("" + row
+ " " + column
);
655 rows
.get(row
).add(cell
);
656 columns
.get(column
).add(cell
);
658 if (columns
.size() < gridColumns
) {
659 columns
.add(new Column(column
+ 1));
662 if (row
< gridRows
- 1) {
663 rows
.add(new Row(row
+ 1));
666 for (int i
= 0; i
< rows
.size(); i
++) {
667 rows
.get(i
).setY(i
+ (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0));
669 for (int j
= 0; j
< columns
.size(); j
++) {
670 columns
.get(j
).setX((j
* (COLUMN_DEFAULT_WIDTH
+ 1)) +
671 (showRowLabels ? ROW_LABEL_WIDTH
: 0));
673 activate(columns
.get(selectedColumn
).get(selectedRow
));
679 * Public constructor.
681 * @param parent parent widget
682 * @param x column relative to parent
683 * @param y row relative to parent
684 * @param width width of widget
685 * @param height height of widget
687 public TTableWidget(final TWidget parent
, final int x
, final int y
,
688 final int width
, final int height
) {
690 this(parent
, x
, y
, width
, height
,
691 width
/ (COLUMN_DEFAULT_WIDTH
+ 1) + EXTRA_COLUMNS
,
692 height
+ EXTRA_ROWS
);
695 // ------------------------------------------------------------------------
696 // Event handlers ---------------------------------------------------------
697 // ------------------------------------------------------------------------
700 * Handle mouse press events.
702 * @param mouse mouse button press event
705 public void onMouseDown(final TMouseEvent mouse
) {
706 if (mouse
.isMouseWheelUp() || mouse
.isMouseWheelDown()) {
707 // Treat wheel up/down as 3 up/down
708 TKeypressEvent keyEvent
;
709 if (mouse
.isMouseWheelUp()) {
710 keyEvent
= new TKeypressEvent(kbUp
);
712 keyEvent
= new TKeypressEvent(kbDown
);
714 for (int i
= 0; i
< 3; i
++) {
715 onKeypress(keyEvent
);
720 // Use TWidget's code to pass the event to the children.
721 super.onMouseDown(mouse
);
727 * @param keypress keystroke event
730 public void onKeypress(final TKeypressEvent keypress
) {
731 if (keypress
.equals(kbTab
)
732 || keypress
.equals(kbShiftTab
)
734 // Squash tab and back-tab. They don't make sense in the TTable
739 // If editing, pass to that cell and do nothing else.
740 if (getSelectedCell().isEditing
) {
741 super.onKeypress(keypress
);
745 if (keypress
.equals(kbLeft
)) {
747 if (selectedColumn
> 0) {
750 activate(columns
.get(selectedColumn
).get(selectedRow
));
751 } else if (keypress
.equals(kbRight
)) {
753 if (selectedColumn
< columns
.size() - 1) {
756 activate(columns
.get(selectedColumn
).get(selectedRow
));
757 } else if (keypress
.equals(kbUp
)) {
759 if (selectedRow
> 0) {
762 activate(columns
.get(selectedColumn
).get(selectedRow
));
763 } else if (keypress
.equals(kbDown
)) {
765 if (selectedRow
< rows
.size() - 1) {
768 activate(columns
.get(selectedColumn
).get(selectedRow
));
769 } else if (keypress
.equals(kbHome
)) {
770 // Home - leftmost column
772 activate(columns
.get(selectedColumn
).get(selectedRow
));
773 } else if (keypress
.equals(kbEnd
)) {
774 // End - rightmost column
775 selectedColumn
= columns
.size() - 1;
776 activate(columns
.get(selectedColumn
).get(selectedRow
));
777 } else if (keypress
.equals(kbPgUp
)) {
778 // PgUp - Treat like multiple up
779 for (int i
= 0; i
< getHeight() - 2; i
++) {
780 if (selectedRow
> 0) {
784 activate(columns
.get(selectedColumn
).get(selectedRow
));
785 } else if (keypress
.equals(kbPgDn
)) {
786 // PgDn - Treat like multiple up
787 for (int i
= 0; i
< getHeight() - 2; i
++) {
788 if (selectedRow
< rows
.size() - 1) {
792 activate(columns
.get(selectedColumn
).get(selectedRow
));
793 } else if (keypress
.equals(kbCtrlHome
)) {
794 // Ctrl-Home - go to top-left
797 activate(columns
.get(selectedColumn
).get(selectedRow
));
798 activate(columns
.get(selectedColumn
).get(selectedRow
));
799 } else if (keypress
.equals(kbCtrlEnd
)) {
800 // Ctrl-End - go to bottom-right
801 selectedRow
= rows
.size() - 1;
802 selectedColumn
= columns
.size() - 1;
803 activate(columns
.get(selectedColumn
).get(selectedRow
));
804 activate(columns
.get(selectedColumn
).get(selectedRow
));
807 super.onKeypress(keypress
);
810 // We may have scrolled off screen. Reset positions as needed to
811 // make the newly selected cell visible.
816 * Handle widget resize events.
818 * @param event resize event
821 public void onResize(final TResizeEvent event
) {
822 super.onResize(event
);
827 // ------------------------------------------------------------------------
828 // TWidget ----------------------------------------------------------------
829 // ------------------------------------------------------------------------
832 * Draw the table row/column labels, and borders.
836 CellAttributes labelColor
= getTheme().getColor("ttable.label");
837 CellAttributes labelColorSelected
= getTheme().getColor("ttable.label.selected");
838 CellAttributes borderColor
= getTheme().getColor("ttable.border");
841 if (showColumnLabels
== true) {
842 for (int i
= left
; i
< columns
.size(); i
++) {
843 if (columns
.get(i
).get(top
).isVisible() == false) {
846 putStringXY(columns
.get(i
).get(top
).getX(), 0,
847 String
.format(" %-" +
848 (columns
.get(i
).width
- 2)
849 + "s ", columns
.get(i
).label
),
850 (i
== selectedColumn ? labelColorSelected
: labelColor
));
855 if (showRowLabels
== true) {
856 for (int i
= top
; i
< rows
.size(); i
++) {
857 if (rows
.get(i
).get(left
).isVisible() == false) {
860 putStringXY(0, rows
.get(i
).get(left
).getY(),
861 String
.format(" %-6s ", rows
.get(i
).label
),
862 (i
== selectedRow ? labelColorSelected
: labelColor
));
866 // Draw vertical borders.
867 if (leftBorder
== Border
.SINGLE
) {
868 vLineXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
869 (topBorder
== Border
.NONE ?
0 : 1) +
870 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
871 getHeight(), '\u2502', borderColor
);
873 for (int i
= left
; i
< columns
.size(); i
++) {
874 if (columns
.get(i
).get(top
).isVisible() == false) {
877 if (columns
.get(i
).rightBorder
== Border
.SINGLE
) {
878 vLineXY(columns
.get(i
).getX() + columns
.get(i
).width
,
879 (topBorder
== Border
.NONE ?
0 : 1) +
880 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
881 getHeight(), '\u2502', borderColor
);
885 // Draw horizontal borders.
886 if (topBorder
== Border
.SINGLE
) {
887 hLineXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
888 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
889 getWidth(), '\u2500', borderColor
);
891 for (int i
= top
; i
< rows
.size(); i
++) {
892 if (rows
.get(i
).get(left
).isVisible() == false) {
895 if (rows
.get(i
).bottomBorder
== Border
.SINGLE
) {
896 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
897 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
898 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
899 getWidth(), '\u2500', borderColor
);
900 } else if (rows
.get(i
).bottomBorder
== Border
.DOUBLE
) {
901 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
902 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
903 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
904 getWidth(), '\u2550', borderColor
);
905 } else if (rows
.get(i
).bottomBorder
== Border
.THICK
) {
906 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
907 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
908 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
909 getWidth(), '\u2501', borderColor
);
912 // Top-left corner if needed
913 if ((topBorder
== Border
.SINGLE
) && (leftBorder
== Border
.SINGLE
)) {
914 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
915 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
916 '\u250c', borderColor
);
919 // Now draw the correct corners
920 for (int i
= top
; i
< rows
.size(); i
++) {
921 if (rows
.get(i
).get(left
).isVisible() == false) {
924 for (int j
= left
; j
< columns
.size(); j
++) {
925 if (columns
.get(j
).get(i
).isVisible() == false) {
928 if ((i
== top
) && (topBorder
== Border
.SINGLE
)
929 && (columns
.get(j
).rightBorder
== Border
.SINGLE
)
932 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
933 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
934 '\u252c', borderColor
);
936 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
937 && (rows
.get(i
).bottomBorder
== Border
.SINGLE
)
940 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
941 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
942 '\u251c', borderColor
);
944 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
945 && (rows
.get(i
).bottomBorder
== Border
.SINGLE
)
947 // Intersection of single bars
948 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
949 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
950 '\u253c', borderColor
);
952 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
953 && (rows
.get(i
).bottomBorder
== Border
.DOUBLE
)
955 // Left tee: single bar vertical, double bar horizontal
956 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
957 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
958 '\u255e', borderColor
);
960 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
961 && (rows
.get(i
).bottomBorder
== Border
.THICK
)
963 // Left tee: single bar vertical, thick bar horizontal
964 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
965 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
966 '\u251d', borderColor
);
968 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
969 && (rows
.get(i
).bottomBorder
== Border
.DOUBLE
)
971 // Intersection: single bar vertical, double bar
973 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
974 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
975 '\u256a', borderColor
);
977 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
978 && (rows
.get(i
).bottomBorder
== Border
.THICK
)
980 // Intersection: single bar vertical, thick bar
982 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
983 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
984 '\u253f', borderColor
);
989 // Now draw the window borders.
993 // ------------------------------------------------------------------------
994 // TTable -----------------------------------------------------------------
995 // ------------------------------------------------------------------------
998 * Generate the default letter name for a column number.
1000 * @param col column number to use for this column. Column 0 will be
1001 * "A", column 1 will be "B", column 26 will be "AA", and so on.
1003 private String
makeColumnLabel(int col
) {
1004 StringBuilder sb
= new StringBuilder();
1006 sb
.append((char) ('A' + (col
% 26)));
1012 return sb
.reverse().toString();
1016 * Get the currently-selected cell.
1018 * @return the selected cell
1020 public Cell
getSelectedCell() {
1021 assert (rows
.get(selectedRow
) != null);
1022 assert (rows
.get(selectedRow
).get(selectedColumn
) != null);
1023 assert (columns
.get(selectedColumn
) != null);
1024 assert (columns
.get(selectedColumn
).get(selectedRow
) != null);
1025 assert (rows
.get(selectedRow
).get(selectedColumn
) ==
1026 columns
.get(selectedColumn
).get(selectedRow
));
1028 return (columns
.get(selectedColumn
).get(selectedRow
));
1032 * Get the currently-selected column.
1034 * @return the selected column
1036 public Column
getSelectedColumn() {
1037 assert (selectedColumn
>= 0);
1038 assert (columns
.size() > selectedColumn
);
1039 assert (columns
.get(selectedColumn
) != null);
1040 return columns
.get(selectedColumn
);
1044 * Get the currently-selected row.
1046 * @return the selected row
1048 public Row
getSelectedRow() {
1049 assert (selectedRow
>= 0);
1050 assert (rows
.size() > selectedRow
);
1051 assert (rows
.get(selectedRow
) != null);
1052 return rows
.get(selectedRow
);
1056 * Get the currently-selected column number. 0 is the left-most column.
1058 * @return the selected column number
1060 public int getSelectedColumnNumber() {
1061 return selectedColumn
;
1065 * Set the currently-selected column number. 0 is the left-most column.
1067 * @param column the column number to select
1069 public void setSelectedColumnNumber(final int column
) {
1070 if ((column
< 0) || (column
> columns
.size() - 1)) {
1071 throw new IndexOutOfBoundsException("Column count is " +
1072 columns
.size() + ", requested index " + column
);
1074 selectedColumn
= column
;
1075 activate(columns
.get(selectedColumn
).get(selectedRow
));
1080 * Get the currently-selected row number. 0 is the top-most row.
1082 * @return the selected row number
1084 public int getSelectedRowNumber() {
1089 * Set the currently-selected row number. 0 is the left-most column.
1091 * @param row the row number to select
1093 public void setSelectedRowNumber(final int row
) {
1094 if ((row
< 0) || (row
> rows
.size() - 1)) {
1095 throw new IndexOutOfBoundsException("Row count is " +
1096 rows
.size() + ", requested index " + row
);
1099 activate(columns
.get(selectedColumn
).get(selectedRow
));
1104 * Get the highlight row flag.
1106 * @return true if the selected row is highlighted
1108 public boolean getHighlightRow() {
1109 return highlightRow
;
1113 * Set the highlight row flag.
1115 * @param highlightRow if true, the selected row will be highlighted
1117 public void setHighlightRow(final boolean highlightRow
) {
1118 this.highlightRow
= highlightRow
;
1122 * Get the highlight column flag.
1124 * @return true if the selected column is highlighted
1126 public boolean getHighlightColumn() {
1127 return highlightColumn
;
1131 * Set the highlight column flag.
1133 * @param highlightColumn if true, the selected column will be highlighted
1135 public void setHighlightColumn(final boolean highlightColumn
) {
1136 this.highlightColumn
= highlightColumn
;
1140 * Get the show row labels flag.
1142 * @return true if row labels are shown
1144 public boolean getShowRowLabels() {
1145 return showRowLabels
;
1149 * Set the show row labels flag.
1151 * @param showRowLabels if true, the row labels will be shown
1153 public void setShowRowLabels(final boolean showRowLabels
) {
1154 this.showRowLabels
= showRowLabels
;
1158 * Get the show column labels flag.
1160 * @return true if column labels are shown
1162 public boolean getShowColumnLabels() {
1163 return showColumnLabels
;
1167 * Set the show column labels flag.
1169 * @param showColumnLabels if true, the column labels will be shown
1171 public void setShowColumnLabels(final boolean showColumnLabels
) {
1172 this.showColumnLabels
= showColumnLabels
;
1176 * Get the number of columns.
1178 * @return the number of columns
1180 public int getColumnCount() {
1181 return columns
.size();
1185 * Get the number of rows.
1187 * @return the number of rows
1189 public int getRowCount() {
1195 * Push top and left to the bottom-most right corner of the available
1198 private void bottomRightCorner() {
1199 int viewColumns
= getWidth();
1200 if (showRowLabels
== true) {
1201 viewColumns
-= ROW_LABEL_WIDTH
;
1204 // Set left and top such that the table stays on screen if possible.
1205 top
= rows
.size() - getHeight();
1206 left
= columns
.size() - (getWidth() / (viewColumns
/ (COLUMN_DEFAULT_WIDTH
+ 1)));
1207 // Now ensure the selection is visible.
1212 * Align the grid so that the selected cell is fully visible.
1214 private void alignGrid() {
1217 System.err.println("alignGrid() # columns " + columns.size() +
1218 " # rows " + rows.size());
1221 int viewColumns
= getWidth();
1222 if (showRowLabels
== true) {
1223 viewColumns
-= ROW_LABEL_WIDTH
;
1225 if (leftBorder
!= Border
.NONE
) {
1228 int viewRows
= getHeight();
1229 if (showColumnLabels
== true) {
1230 viewRows
-= COLUMN_LABEL_HEIGHT
;
1232 if (topBorder
!= Border
.NONE
) {
1236 // If we pushed left or right, adjust the box to include the new
1238 if (selectedColumn
< left
) {
1239 left
= selectedColumn
- 1;
1244 if (selectedRow
< top
) {
1245 top
= selectedRow
- 1;
1252 * viewColumns and viewRows now contain the available columns and
1253 * rows available to view the selected cell. We adjust left and top
1254 * to ensure the selected cell is within view, and then make all
1255 * cells outside the box between (left, top) and (right, bottom)
1258 * We need to calculate right and bottom now.
1262 boolean done
= false;
1264 int rightCellX
= (showRowLabels ? ROW_LABEL_WIDTH
: 0);
1265 if (leftBorder
!= Border
.NONE
) {
1268 int maxCellX
= rightCellX
+ viewColumns
;
1270 boolean selectedIsVisible
= false;
1272 for (int x
= left
; x
< columns
.size(); x
++) {
1273 if (x
== selectedColumn
) {
1274 selectedX
= rightCellX
;
1275 if (selectedX
+ columns
.get(x
).width
+ 1 <= maxCellX
) {
1276 selectedIsVisible
= true;
1279 rightCellX
+= columns
.get(x
).width
+ 1;
1280 if (rightCellX
>= maxCellX
) {
1285 if (right
< selectedColumn
) {
1286 // selectedColumn is outside the view range. Push left over,
1287 // and calculate again.
1289 } else if (left
== selectedColumn
) {
1290 // selectedColumn doesn't fit inside the view range, but we
1291 // can't go over any further either. Bail out.
1293 } else if (selectedIsVisible
== false) {
1294 // selectedColumn doesn't fit inside the view range, continue
1298 // selectedColumn is fully visible, all done.
1299 assert (selectedIsVisible
== true);
1305 // We have the left/right range correct, set cell visibility and
1306 // column X positions.
1307 int leftCellX
= showRowLabels ? ROW_LABEL_WIDTH
: 0;
1308 if (leftBorder
!= Border
.NONE
) {
1311 for (int x
= 0; x
< columns
.size(); x
++) {
1312 if ((x
< left
) || (x
> right
)) {
1313 for (int i
= 0; i
< rows
.size(); i
++) {
1314 columns
.get(x
).get(i
).setVisible(false);
1315 columns
.get(x
).setX(getWidth() + 1);
1319 for (int i
= 0; i
< rows
.size(); i
++) {
1320 columns
.get(x
).get(i
).setVisible(true);
1322 columns
.get(x
).setX(leftCellX
);
1323 leftCellX
+= columns
.get(x
).width
+ 1;
1330 int bottomCellY
= (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0);
1331 if (topBorder
!= Border
.NONE
) {
1334 int maxCellY
= bottomCellY
+ viewRows
;
1336 for (int y
= top
; y
< rows
.size(); y
++) {
1337 bottomCellY
+= rows
.get(y
).height
;
1338 if (bottomCellY
>= maxCellY
) {
1343 if (bottom
< selectedRow
) {
1344 // selectedRow is outside the view range. Push top down, and
1348 // selectedRow is inside the view range, done.
1353 // We have the top/bottom range correct, set cell visibility and
1355 int topCellY
= showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0;
1356 if (topBorder
!= Border
.NONE
) {
1359 for (int y
= 0; y
< rows
.size(); y
++) {
1360 if ((y
< top
) || (y
> bottom
)) {
1361 for (int i
= 0; i
< columns
.size(); i
++) {
1362 rows
.get(y
).get(i
).setVisible(false);
1364 rows
.get(y
).setY(getHeight() + 1);
1367 for (int i
= 0; i
< columns
.size(); i
++) {
1368 rows
.get(y
).get(i
).setVisible(true);
1370 rows
.get(y
).setY(topCellY
);
1371 topCellY
+= rows
.get(y
).height
;
1374 // Last thing: cancel any edits that are not the selected cell.
1375 for (int y
= 0; y
< rows
.size(); y
++) {
1376 for (int x
= 0; x
< columns
.size(); x
++) {
1377 if ((x
== selectedColumn
) && (y
== selectedRow
)) {
1380 rows
.get(y
).get(x
).cancelEdit();
1386 * Load contents from file in CSV format.
1388 * @param csvFile a File referencing the CSV data
1389 * @throws IOException if a java.io operation throws
1391 public void loadCsvFile(final File csvFile
) throws IOException
{
1392 BufferedReader reader
= null;
1395 reader
= new BufferedReader(new FileReader(csvFile
));
1398 boolean first
= true;
1399 for (line
= reader
.readLine(); line
!= null;
1400 line
= reader
.readLine()) {
1402 List
<String
> list
= StringUtils
.fromCsv(line
);
1403 if (list
.size() == 0) {
1407 if (list
.size() > columns
.size()) {
1408 int n
= list
.size() - columns
.size();
1409 for (int i
= 0; i
< n
; i
++) {
1410 selectedColumn
= columns
.size() - 1;
1411 insertColumnRight(selectedColumn
);
1414 assert (list
.size() == columns
.size());
1417 // First row: just replace what is here.
1421 // All other rows: append to the end.
1422 selectedRow
= rows
.size() - 1;
1423 insertRowBelow(selectedRow
);
1424 selectedRow
= rows
.size() - 1;
1426 for (int i
= 0; i
< list
.size(); i
++) {
1427 rows
.get(selectedRow
).get(i
).setText(list
.get(i
));
1431 if (reader
!= null) {
1441 activate(columns
.get(selectedColumn
).get(selectedRow
));
1445 * Save contents to file in CSV format.
1447 * @param filename file to save to
1448 * @throws IOException if a java.io operation throws
1450 public void saveToCsvFilename(final String filename
) throws IOException
{
1451 BufferedWriter writer
= null;
1454 writer
= new BufferedWriter(new FileWriter(filename
));
1455 for (Row row
: rows
) {
1456 List
<String
> list
= new ArrayList
<String
>(row
.cells
.size());
1457 for (Cell cell
: row
.cells
) {
1458 list
.add(cell
.getText());
1460 writer
.write(StringUtils
.toCsv(list
));
1464 if (writer
!= null) {
1471 * Save contents to file in text format with lines.
1473 * @param filename file to save to
1474 * @throws IOException if a java.io operation throws
1476 public void saveToTextFilename(final String filename
) throws IOException
{
1477 BufferedWriter writer
= null;
1480 writer
= new BufferedWriter(new FileWriter(filename
));
1482 if ((topBorder
== Border
.SINGLE
) && (leftBorder
== Border
.SINGLE
)) {
1483 // Emit top-left corner.
1484 writer
.write("\u250c");
1487 if (topBorder
== Border
.SINGLE
) {
1489 for (Cell cell
: rows
.get(0).cells
) {
1490 for (int i
= 0; i
< columns
.get(cellI
).width
; i
++) {
1491 writer
.write("\u2500");
1494 if (columns
.get(cellI
).rightBorder
== Border
.SINGLE
) {
1495 if (cellI
< columns
.size() - 1) {
1497 writer
.write("\u252c");
1499 // Emit top-right corner.
1500 writer
.write("\u2510");
1509 for (Row row
: rows
) {
1511 if (leftBorder
== Border
.SINGLE
) {
1512 // Emit left border.
1513 writer
.write("\u2502");
1517 for (Cell cell
: row
.cells
) {
1518 writer
.write(String
.format("%" +
1519 columns
.get(cellI
).width
+ "s", cell
.getText()));
1521 if (columns
.get(cellI
).rightBorder
== Border
.SINGLE
) {
1522 // Emit right border.
1523 writer
.write("\u2502");
1529 if (row
.bottomBorder
== Border
.NONE
) {
1530 // All done, move on to the next row.
1534 // Emit the bottom borders and intersections.
1535 if ((leftBorder
== Border
.SINGLE
)
1536 && (row
.bottomBorder
!= Border
.NONE
)
1538 if (rowI
< rows
.size() - 1) {
1539 if (row
.bottomBorder
== Border
.SINGLE
) {
1541 writer
.write("\u251c");
1542 } else if (row
.bottomBorder
== Border
.DOUBLE
) {
1543 // Emit left tee (double).
1544 writer
.write("\u255e");
1545 } else if (row
.bottomBorder
== Border
.THICK
) {
1546 // Emit left tee (thick).
1547 writer
.write("\u251d");
1551 if (rowI
== rows
.size() - 1) {
1552 if (row
.bottomBorder
== Border
.SINGLE
) {
1553 // Emit left bottom corner.
1554 writer
.write("\u2514");
1555 } else if (row
.bottomBorder
== Border
.DOUBLE
) {
1556 // Emit left bottom corner (double).
1557 writer
.write("\u2558");
1558 } else if (row
.bottomBorder
== Border
.THICK
) {
1559 // Emit left bottom corner (thick).
1560 writer
.write("\u2515");
1566 for (Cell cell
: row
.cells
) {
1568 for (int i
= 0; i
< columns
.get(cellI
).width
; i
++) {
1569 if (row
.bottomBorder
== Border
.SINGLE
) {
1570 writer
.write("\u2500");
1572 if (row
.bottomBorder
== Border
.DOUBLE
) {
1573 writer
.write("\u2550");
1575 if (row
.bottomBorder
== Border
.THICK
) {
1576 writer
.write("\u2501");
1580 if ((rowI
< rows
.size() - 1)
1581 && (cellI
== columns
.size() - 1)
1582 && (row
.bottomBorder
== Border
.SINGLE
)
1583 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1586 writer
.write("\u2524");
1588 if ((rowI
< rows
.size() - 1)
1589 && (cellI
== columns
.size() - 1)
1590 && (row
.bottomBorder
== Border
.DOUBLE
)
1591 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1593 // Emit right tee (double).
1594 writer
.write("\u2561");
1596 if ((rowI
< rows
.size() - 1)
1597 && (cellI
== columns
.size() - 1)
1598 && (row
.bottomBorder
== Border
.THICK
)
1599 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1601 // Emit right tee (thick).
1602 writer
.write("\u2525");
1604 if ((rowI
== rows
.size() - 1)
1605 && (cellI
== columns
.size() - 1)
1606 && (row
.bottomBorder
== Border
.SINGLE
)
1607 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1609 // Emit right bottom corner.
1610 writer
.write("\u2518");
1612 if ((rowI
== rows
.size() - 1)
1613 && (cellI
== columns
.size() - 1)
1614 && (row
.bottomBorder
== Border
.DOUBLE
)
1615 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1617 // Emit right bottom corner (double).
1618 writer
.write("\u255b");
1620 if ((rowI
== rows
.size() - 1)
1621 && (cellI
== columns
.size() - 1)
1622 && (row
.bottomBorder
== Border
.THICK
)
1623 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1625 // Emit right bottom corner (thick).
1626 writer
.write("\u2519");
1628 if ((rowI
< rows
.size() - 1)
1629 && (cellI
< columns
.size() - 1)
1630 && (row
.bottomBorder
== Border
.SINGLE
)
1631 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1633 // Emit intersection.
1634 writer
.write("\u253c");
1636 if ((rowI
< rows
.size() - 1)
1637 && (cellI
< columns
.size() - 1)
1638 && (row
.bottomBorder
== Border
.DOUBLE
)
1639 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1641 // Emit intersection (double).
1642 writer
.write("\u256a");
1644 if ((rowI
< rows
.size() - 1)
1645 && (cellI
< columns
.size() - 1)
1646 && (row
.bottomBorder
== Border
.THICK
)
1647 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1649 // Emit intersection (thick).
1650 writer
.write("\u253f");
1652 if ((rowI
== rows
.size() - 1)
1653 && (cellI
< columns
.size() - 1)
1654 && (row
.bottomBorder
== Border
.SINGLE
)
1655 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1658 writer
.write("\u2534");
1660 if ((rowI
== rows
.size() - 1)
1661 && (cellI
< columns
.size() - 1)
1662 && (row
.bottomBorder
== Border
.DOUBLE
)
1663 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1665 // Emit bottom tee (double).
1666 writer
.write("\u2567");
1668 if ((rowI
== rows
.size() - 1)
1669 && (cellI
< columns
.size() - 1)
1670 && (row
.bottomBorder
== Border
.THICK
)
1671 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1673 // Emit bottom tee (thick).
1674 writer
.write("\u2537");
1684 if (writer
!= null) {
1691 * Set the selected cell location.
1693 * @param column the selected cell location column
1694 * @param row the selected cell location row
1696 public void setSelectedCell(final int column
, final int row
) {
1697 if ((column
< 0) || (column
> columns
.size() - 1)) {
1698 throw new IndexOutOfBoundsException("Column count is " +
1699 columns
.size() + ", requested index " + column
);
1701 if ((row
< 0) || (row
> rows
.size() - 1)) {
1702 throw new IndexOutOfBoundsException("Row count is " +
1703 rows
.size() + ", requested index " + row
);
1705 selectedColumn
= column
;
1711 * Get a particular cell.
1713 * @param column the cell column
1714 * @param row the cell row
1717 public Cell
getCell(final int column
, final int row
) {
1718 if ((column
< 0) || (column
> columns
.size() - 1)) {
1719 throw new IndexOutOfBoundsException("Column count is " +
1720 columns
.size() + ", requested index " + column
);
1722 if ((row
< 0) || (row
> rows
.size() - 1)) {
1723 throw new IndexOutOfBoundsException("Row count is " +
1724 rows
.size() + ", requested index " + row
);
1726 return rows
.get(row
).get(column
);
1730 * Get the text of a particular cell.
1732 * @param column the cell column
1733 * @param row the cell row
1734 * @return the text in the cell
1736 public String
getCellText(final int column
, final int row
) {
1737 if ((column
< 0) || (column
> columns
.size() - 1)) {
1738 throw new IndexOutOfBoundsException("Column count is " +
1739 columns
.size() + ", requested index " + column
);
1741 if ((row
< 0) || (row
> rows
.size() - 1)) {
1742 throw new IndexOutOfBoundsException("Row count is " +
1743 rows
.size() + ", requested index " + row
);
1745 return rows
.get(row
).get(column
).getText();
1749 * Set the text of a particular cell.
1751 * @param column the cell column
1752 * @param row the cell row
1753 * @param text the text to put into the cell
1755 public void setCellText(final int column
, final int row
,
1756 final String text
) {
1758 if ((column
< 0) || (column
> columns
.size() - 1)) {
1759 throw new IndexOutOfBoundsException("Column count is " +
1760 columns
.size() + ", requested index " + column
);
1762 if ((row
< 0) || (row
> rows
.size() - 1)) {
1763 throw new IndexOutOfBoundsException("Row count is " +
1764 rows
.size() + ", requested index " + row
);
1766 rows
.get(row
).get(column
).setText(text
);
1770 * Set the action to perform when the user presses enter on a particular
1773 * @param column the cell column
1774 * @param row the cell row
1775 * @param action the action to perform when the user presses enter on the
1778 public void setCellEnterAction(final int column
, final int row
,
1779 final TAction action
) {
1781 if ((column
< 0) || (column
> columns
.size() - 1)) {
1782 throw new IndexOutOfBoundsException("Column count is " +
1783 columns
.size() + ", requested index " + column
);
1785 if ((row
< 0) || (row
> rows
.size() - 1)) {
1786 throw new IndexOutOfBoundsException("Row count is " +
1787 rows
.size() + ", requested index " + row
);
1789 rows
.get(row
).get(column
).field
.setEnterAction(action
);
1793 * Set the action to perform when the user updates a particular cell.
1795 * @param column the cell column
1796 * @param row the cell row
1797 * @param action the action to perform when the user updates the cell
1799 public void setCellUpdateAction(final int column
, final int row
,
1800 final TAction action
) {
1802 if ((column
< 0) || (column
> columns
.size() - 1)) {
1803 throw new IndexOutOfBoundsException("Column count is " +
1804 columns
.size() + ", requested index " + column
);
1806 if ((row
< 0) || (row
> rows
.size() - 1)) {
1807 throw new IndexOutOfBoundsException("Row count is " +
1808 rows
.size() + ", requested index " + row
);
1810 rows
.get(row
).get(column
).field
.setUpdateAction(action
);
1814 * Get the width of a column.
1816 * @param column the column number
1817 * @return the width of the column
1819 public int getColumnWidth(final int column
) {
1820 if ((column
< 0) || (column
> columns
.size() - 1)) {
1821 throw new IndexOutOfBoundsException("Column count is " +
1822 columns
.size() + ", requested index " + column
);
1824 return columns
.get(column
).width
;
1828 * Set the width of a column.
1830 * @param column the column number
1831 * @param width the new width of the column
1833 public void setColumnWidth(final int column
, final int width
) {
1834 if ((column
< 0) || (column
> columns
.size() - 1)) {
1835 throw new IndexOutOfBoundsException("Column count is " +
1836 columns
.size() + ", requested index " + column
);
1840 // Columns may not be smaller than 4 cells wide.
1844 int delta
= width
- columns
.get(column
).width
;
1845 columns
.get(column
).width
= width
;
1846 for (Cell cell
: columns
.get(column
).cells
) {
1847 cell
.setWidth(columns
.get(column
).width
);
1848 cell
.field
.setWidth(columns
.get(column
).width
);
1850 for (int i
= column
+ 1; i
< columns
.size(); i
++) {
1851 columns
.get(i
).setX(columns
.get(i
).getX() + delta
);
1853 if (column
== columns
.size() - 1) {
1854 bottomRightCorner();
1861 * Get the label of a column.
1863 * @param column the column number
1864 * @return the label of the column
1866 public String
getColumnLabel(final int column
) {
1867 if ((column
< 0) || (column
> columns
.size() - 1)) {
1868 throw new IndexOutOfBoundsException("Column count is " +
1869 columns
.size() + ", requested index " + column
);
1871 return columns
.get(column
).label
;
1875 * Set the label of a column.
1877 * @param column the column number
1878 * @param label the new label of the column
1880 public void setColumnLabel(final int column
, final String label
) {
1881 if ((column
< 0) || (column
> columns
.size() - 1)) {
1882 throw new IndexOutOfBoundsException("Column count is " +
1883 columns
.size() + ", requested index " + column
);
1885 columns
.get(column
).label
= label
;
1889 * Get the label of a row.
1891 * @param row the row number
1892 * @return the label of the row
1894 public String
getRowLabel(final int row
) {
1895 if ((row
< 0) || (row
> rows
.size() - 1)) {
1896 throw new IndexOutOfBoundsException("Row count is " +
1897 rows
.size() + ", requested index " + row
);
1899 return rows
.get(row
).label
;
1903 * Set the label of a row.
1905 * @param row the row number
1906 * @param label the new label of the row
1908 public void setRowLabel(final int row
, final String label
) {
1909 if ((row
< 0) || (row
> rows
.size() - 1)) {
1910 throw new IndexOutOfBoundsException("Row count is " +
1911 rows
.size() + ", requested index " + row
);
1913 rows
.get(row
).label
= label
;
1917 * Insert one row at a particular index.
1919 * @param idx the row number
1921 private void insertRowAt(final int idx
) {
1922 Row newRow
= new Row(idx
);
1923 for (int i
= 0; i
< columns
.size(); i
++) {
1924 Cell cell
= new Cell(this, columns
.get(i
).getX(),
1925 rows
.get(idx
).getY(), COLUMN_DEFAULT_WIDTH
, 1, i
, idx
);
1927 columns
.get(i
).cells
.add(idx
, cell
);
1929 rows
.add(idx
, newRow
);
1931 for (int x
= 0; x
< columns
.size(); x
++) {
1932 for (int y
= idx
; y
< rows
.size(); y
++) {
1933 columns
.get(x
).get(y
).row
= y
;
1934 columns
.get(x
).get(y
).column
= x
;
1937 for (int i
= idx
+ 1; i
< rows
.size(); i
++) {
1938 String oldRowLabel
= Integer
.toString(i
- 1);
1939 if (rows
.get(i
).label
.equals(oldRowLabel
)) {
1940 rows
.get(i
).label
= Integer
.toString(i
);
1947 * Insert one row above a particular row.
1949 * @param row the row number
1951 public void insertRowAbove(final int row
) {
1952 if ((row
< 0) || (row
> rows
.size() - 1)) {
1953 throw new IndexOutOfBoundsException("Row count is " +
1954 rows
.size() + ", requested index " + row
);
1958 activate(columns
.get(selectedColumn
).get(selectedRow
));
1962 * Insert one row below a particular row.
1964 * @param row the row number
1966 public void insertRowBelow(final int row
) {
1967 if ((row
< 0) || (row
> rows
.size() - 1)) {
1968 throw new IndexOutOfBoundsException("Row count is " +
1969 rows
.size() + ", requested index " + row
);
1972 if (idx
< rows
.size()) {
1974 activate(columns
.get(selectedColumn
).get(selectedRow
));
1978 // row is the last row, we need to perform an append.
1979 Row newRow
= new Row(idx
);
1980 for (int i
= 0; i
< columns
.size(); i
++) {
1981 Cell cell
= new Cell(this, columns
.get(i
).getX(),
1982 rows
.get(row
).getY(), COLUMN_DEFAULT_WIDTH
, 1, i
, idx
);
1984 columns
.get(i
).cells
.add(cell
);
1988 activate(columns
.get(selectedColumn
).get(selectedRow
));
1992 * Delete a particular row.
1994 * @param row the row number
1996 public void deleteRow(final int row
) {
1997 if ((row
< 0) || (row
> rows
.size() - 1)) {
1998 throw new IndexOutOfBoundsException("Row count is " +
1999 rows
.size() + ", requested index " + row
);
2001 if (rows
.size() == 1) {
2002 // Don't delete the last row.
2005 for (int i
= 0; i
< columns
.size(); i
++) {
2006 Cell cell
= columns
.get(i
).cells
.remove(row
);
2007 getChildren().remove(cell
);
2011 for (int x
= 0; x
< columns
.size(); x
++) {
2012 for (int y
= row
; y
< rows
.size(); y
++) {
2013 columns
.get(x
).get(y
).row
= y
;
2014 columns
.get(x
).get(y
).column
= x
;
2017 for (int i
= row
; i
< rows
.size(); i
++) {
2018 String oldRowLabel
= Integer
.toString(i
+ 1);
2019 if (rows
.get(i
).label
.equals(oldRowLabel
)) {
2020 rows
.get(i
).label
= Integer
.toString(i
);
2023 if (selectedRow
== rows
.size()) {
2026 activate(columns
.get(selectedColumn
).get(selectedRow
));
2027 bottomRightCorner();
2031 * Insert one column at a particular index.
2033 * @param idx the column number
2035 private void insertColumnAt(final int idx
) {
2036 Column newColumn
= new Column(idx
);
2037 for (int i
= 0; i
< rows
.size(); i
++) {
2038 Cell cell
= new Cell(this, columns
.get(idx
).getX(),
2039 rows
.get(i
).getY(), COLUMN_DEFAULT_WIDTH
, 1, idx
, i
);
2040 newColumn
.add(cell
);
2041 rows
.get(i
).cells
.add(idx
, cell
);
2043 columns
.add(idx
, newColumn
);
2045 for (int x
= idx
; x
< columns
.size(); x
++) {
2046 for (int y
= 0; y
< rows
.size(); y
++) {
2047 columns
.get(x
).get(y
).row
= y
;
2048 columns
.get(x
).get(y
).column
= x
;
2051 for (int i
= idx
+ 1; i
< columns
.size(); i
++) {
2052 String oldColumnLabel
= makeColumnLabel(i
- 1);
2053 if (columns
.get(i
).label
.equals(oldColumnLabel
)) {
2054 columns
.get(i
).label
= makeColumnLabel(i
);
2061 * Insert one column to the left of a particular column.
2063 * @param column the column number
2065 public void insertColumnLeft(final int column
) {
2066 if ((column
< 0) || (column
> columns
.size() - 1)) {
2067 throw new IndexOutOfBoundsException("Column count is " +
2068 columns
.size() + ", requested index " + column
);
2070 insertColumnAt(column
);
2072 activate(columns
.get(selectedColumn
).get(selectedRow
));
2076 * Insert one column to the right of a particular column.
2078 * @param column the column number
2080 public void insertColumnRight(final int column
) {
2081 if ((column
< 0) || (column
> columns
.size() - 1)) {
2082 throw new IndexOutOfBoundsException("Column count is " +
2083 columns
.size() + ", requested index " + column
);
2085 int idx
= column
+ 1;
2086 if (idx
< columns
.size()) {
2087 insertColumnAt(idx
);
2088 activate(columns
.get(selectedColumn
).get(selectedRow
));
2092 // column is the last column, we need to perform an append.
2093 Column newColumn
= new Column(idx
);
2094 for (int i
= 0; i
< rows
.size(); i
++) {
2095 Cell cell
= new Cell(this, columns
.get(column
).getX(),
2096 rows
.get(i
).getY(), COLUMN_DEFAULT_WIDTH
, 1, idx
, i
);
2097 newColumn
.add(cell
);
2098 rows
.get(i
).cells
.add(cell
);
2100 columns
.add(newColumn
);
2102 activate(columns
.get(selectedColumn
).get(selectedRow
));
2106 * Delete a particular column.
2108 * @param column the column number
2110 public void deleteColumn(final int column
) {
2111 if ((column
< 0) || (column
> columns
.size() - 1)) {
2112 throw new IndexOutOfBoundsException("Column count is " +
2113 columns
.size() + ", requested index " + column
);
2115 if (columns
.size() == 1) {
2116 // Don't delete the last column.
2119 for (int i
= 0; i
< rows
.size(); i
++) {
2120 Cell cell
= rows
.get(i
).cells
.remove(column
);
2121 getChildren().remove(cell
);
2123 columns
.remove(column
);
2125 for (int x
= column
; x
< columns
.size(); x
++) {
2126 for (int y
= 0; y
< rows
.size(); y
++) {
2127 columns
.get(x
).get(y
).row
= y
;
2128 columns
.get(x
).get(y
).column
= x
;
2131 for (int i
= column
; i
< columns
.size(); i
++) {
2132 String oldColumnLabel
= makeColumnLabel(i
+ 1);
2133 if (columns
.get(i
).label
.equals(oldColumnLabel
)) {
2134 columns
.get(i
).label
= makeColumnLabel(i
);
2137 if (selectedColumn
== columns
.size()) {
2140 activate(columns
.get(selectedColumn
).get(selectedRow
));
2141 bottomRightCorner();
2145 * Delete the selected cell, shifting cells over to the left.
2147 public void deleteCellShiftLeft() {
2148 // All we do is copy the text from every cell in this row over.
2149 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
2150 setCellText(i
- 1, selectedRow
, getCellText(i
, selectedRow
));
2152 setCellText(columns
.size() - 1, selectedRow
, "");
2156 * Delete the selected cell, shifting cells from below up.
2158 public void deleteCellShiftUp() {
2159 // All we do is copy the text from every cell in this column up.
2160 for (int i
= selectedRow
+ 1; i
< rows
.size(); i
++) {
2161 setCellText(selectedColumn
, i
- 1, getCellText(selectedColumn
, i
));
2163 setCellText(selectedColumn
, rows
.size() - 1, "");
2167 * Set a particular cell read-only (non-editable) or not.
2169 * @param column the cell column
2170 * @param row the cell row
2171 * @param readOnly if true, the cell will be non-editable
2173 public void setCellReadOnly(final int column
, final int row
,
2174 final boolean readOnly
) {
2176 if ((column
< 0) || (column
> columns
.size() - 1)) {
2177 throw new IndexOutOfBoundsException("Column count is " +
2178 columns
.size() + ", requested index " + column
);
2180 if ((row
< 0) || (row
> rows
.size() - 1)) {
2181 throw new IndexOutOfBoundsException("Row count is " +
2182 rows
.size() + ", requested index " + row
);
2184 rows
.get(row
).get(column
).setReadOnly(readOnly
);
2188 * Set an entire row of cells read-only (non-editable) or not.
2190 * @param row the row number
2191 * @param readOnly if true, the cells will be non-editable
2193 public void setRowReadOnly(final int row
, final boolean readOnly
) {
2194 if ((row
< 0) || (row
> rows
.size() - 1)) {
2195 throw new IndexOutOfBoundsException("Row count is " +
2196 rows
.size() + ", requested index " + row
);
2198 for (Cell cell
: rows
.get(row
).cells
) {
2199 cell
.setReadOnly(readOnly
);
2204 * Set an entire column of cells read-only (non-editable) or not.
2206 * @param column the column number
2207 * @param readOnly if true, the cells will be non-editable
2209 public void setColumnReadOnly(final int column
, final boolean readOnly
) {
2210 if ((column
< 0) || (column
> columns
.size() - 1)) {
2211 throw new IndexOutOfBoundsException("Column count is " +
2212 columns
.size() + ", requested index " + column
);
2214 for (Cell cell
: columns
.get(column
).cells
) {
2215 cell
.setReadOnly(readOnly
);
2220 * Set all borders across the entire table to Border.NONE.
2222 public void setBorderAllNone() {
2223 topBorder
= Border
.NONE
;
2224 leftBorder
= Border
.NONE
;
2225 for (int i
= 0; i
< columns
.size(); i
++) {
2226 columns
.get(i
).rightBorder
= Border
.NONE
;
2228 for (int i
= 0; i
< rows
.size(); i
++) {
2229 rows
.get(i
).bottomBorder
= Border
.NONE
;
2230 rows
.get(i
).height
= 1;
2232 bottomRightCorner();
2236 * Set all borders across the entire table to Border.SINGLE.
2238 public void setBorderAllSingle() {
2239 topBorder
= Border
.SINGLE
;
2240 leftBorder
= Border
.SINGLE
;
2241 for (int i
= 0; i
< columns
.size(); i
++) {
2242 columns
.get(i
).rightBorder
= Border
.SINGLE
;
2244 for (int i
= 0; i
< rows
.size(); i
++) {
2245 rows
.get(i
).bottomBorder
= Border
.SINGLE
;
2246 rows
.get(i
).height
= 2;
2252 * Set all borders around the selected cell to Border.NONE.
2254 public void setBorderCellNone() {
2255 if (selectedRow
== 0) {
2256 topBorder
= Border
.NONE
;
2258 if (selectedColumn
== 0) {
2259 leftBorder
= Border
.NONE
;
2261 if (selectedColumn
> 0) {
2262 columns
.get(selectedColumn
- 1).rightBorder
= Border
.NONE
;
2264 columns
.get(selectedColumn
).rightBorder
= Border
.NONE
;
2265 if (selectedRow
> 0) {
2266 rows
.get(selectedRow
- 1).bottomBorder
= Border
.NONE
;
2267 rows
.get(selectedRow
- 1).height
= 1;
2269 rows
.get(selectedRow
).bottomBorder
= Border
.NONE
;
2270 rows
.get(selectedRow
).height
= 1;
2271 bottomRightCorner();
2275 * Set all borders around the selected cell to Border.SINGLE.
2277 public void setBorderCellSingle() {
2278 if (selectedRow
== 0) {
2279 topBorder
= Border
.SINGLE
;
2281 if (selectedColumn
== 0) {
2282 leftBorder
= Border
.SINGLE
;
2284 if (selectedColumn
> 0) {
2285 columns
.get(selectedColumn
- 1).rightBorder
= Border
.SINGLE
;
2287 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
2288 if (selectedRow
> 0) {
2289 rows
.get(selectedRow
- 1).bottomBorder
= Border
.SINGLE
;
2290 rows
.get(selectedRow
- 1).height
= 2;
2292 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
2293 rows
.get(selectedRow
).height
= 2;
2298 * Set the column border to the right of the selected cell to
2301 public void setBorderColumnRightSingle() {
2302 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
2307 * Set the column border to the right of the selected cell to
2310 public void setBorderColumnLeftSingle() {
2311 if (selectedColumn
== 0) {
2312 leftBorder
= Border
.SINGLE
;
2314 columns
.get(selectedColumn
- 1).rightBorder
= Border
.SINGLE
;
2320 * Set the row border above the selected cell to Border.SINGLE.
2322 public void setBorderRowAboveSingle() {
2323 if (selectedRow
== 0) {
2324 topBorder
= Border
.SINGLE
;
2326 rows
.get(selectedRow
- 1).bottomBorder
= Border
.SINGLE
;
2327 rows
.get(selectedRow
- 1).height
= 2;
2333 * Set the row border below the selected cell to Border.SINGLE.
2335 public void setBorderRowBelowSingle() {
2336 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
2337 rows
.get(selectedRow
).height
= 2;
2342 * Set the row border below the selected cell to Border.DOUBLE.
2344 public void setBorderRowBelowDouble() {
2345 rows
.get(selectedRow
).bottomBorder
= Border
.DOUBLE
;
2346 rows
.get(selectedRow
).height
= 2;
2351 * Set the row border below the selected cell to Border.THICK.
2353 public void setBorderRowBelowThick() {
2354 rows
.get(selectedRow
).bottomBorder
= Border
.THICK
;
2355 rows
.get(selectedRow
).height
= 2;