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
));
1430 // TODO: detect header line
1433 if (reader
!= null) {
1443 activate(columns
.get(selectedColumn
).get(selectedRow
));
1447 * Save contents to file in CSV format.
1449 * @param filename file to save to
1450 * @throws IOException if a java.io operation throws
1452 public void saveToCsvFilename(final String filename
) throws IOException
{
1453 BufferedWriter writer
= null;
1456 writer
= new BufferedWriter(new FileWriter(filename
));
1457 for (Row row
: rows
) {
1458 List
<String
> list
= new ArrayList
<String
>(row
.cells
.size());
1459 for (Cell cell
: row
.cells
) {
1460 list
.add(cell
.getText());
1462 writer
.write(StringUtils
.toCsv(list
));
1466 if (writer
!= null) {
1473 * Save contents to file in text format with lines.
1475 * @param filename file to save to
1476 * @throws IOException if a java.io operation throws
1478 public void saveToTextFilename(final String filename
) throws IOException
{
1479 BufferedWriter writer
= null;
1482 writer
= new BufferedWriter(new FileWriter(filename
));
1484 if ((topBorder
== Border
.SINGLE
) && (leftBorder
== Border
.SINGLE
)) {
1485 // Emit top-left corner.
1486 writer
.write("\u250c");
1489 if (topBorder
== Border
.SINGLE
) {
1491 for (Cell cell
: rows
.get(0).cells
) {
1492 for (int i
= 0; i
< columns
.get(cellI
).width
; i
++) {
1493 writer
.write("\u2500");
1496 if (columns
.get(cellI
).rightBorder
== Border
.SINGLE
) {
1497 if (cellI
< columns
.size() - 1) {
1499 writer
.write("\u252c");
1501 // Emit top-right corner.
1502 writer
.write("\u2510");
1511 for (Row row
: rows
) {
1513 if (leftBorder
== Border
.SINGLE
) {
1514 // Emit left border.
1515 writer
.write("\u2502");
1519 for (Cell cell
: row
.cells
) {
1520 writer
.write(String
.format("%" +
1521 columns
.get(cellI
).width
+ "s", cell
.getText()));
1523 if (columns
.get(cellI
).rightBorder
== Border
.SINGLE
) {
1524 // Emit right border.
1525 writer
.write("\u2502");
1531 if (row
.bottomBorder
== Border
.NONE
) {
1532 // All done, move on to the next row.
1536 // Emit the bottom borders and intersections.
1537 if ((leftBorder
== Border
.SINGLE
)
1538 && (row
.bottomBorder
!= Border
.NONE
)
1540 if (rowI
< rows
.size() - 1) {
1541 if (row
.bottomBorder
== Border
.SINGLE
) {
1543 writer
.write("\u251c");
1544 } else if (row
.bottomBorder
== Border
.DOUBLE
) {
1545 // Emit left tee (double).
1546 writer
.write("\u255e");
1547 } else if (row
.bottomBorder
== Border
.THICK
) {
1548 // Emit left tee (thick).
1549 writer
.write("\u251d");
1553 if (rowI
== rows
.size() - 1) {
1554 if (row
.bottomBorder
== Border
.SINGLE
) {
1555 // Emit left bottom corner.
1556 writer
.write("\u2514");
1557 } else if (row
.bottomBorder
== Border
.DOUBLE
) {
1558 // Emit left bottom corner (double).
1559 writer
.write("\u2558");
1560 } else if (row
.bottomBorder
== Border
.THICK
) {
1561 // Emit left bottom corner (thick).
1562 writer
.write("\u2515");
1568 for (Cell cell
: row
.cells
) {
1570 for (int i
= 0; i
< columns
.get(cellI
).width
; i
++) {
1571 if (row
.bottomBorder
== Border
.SINGLE
) {
1572 writer
.write("\u2500");
1574 if (row
.bottomBorder
== Border
.DOUBLE
) {
1575 writer
.write("\u2550");
1577 if (row
.bottomBorder
== Border
.THICK
) {
1578 writer
.write("\u2501");
1582 if ((rowI
< rows
.size() - 1)
1583 && (cellI
== columns
.size() - 1)
1584 && (row
.bottomBorder
== Border
.SINGLE
)
1585 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1588 writer
.write("\u2524");
1590 if ((rowI
< rows
.size() - 1)
1591 && (cellI
== columns
.size() - 1)
1592 && (row
.bottomBorder
== Border
.DOUBLE
)
1593 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1595 // Emit right tee (double).
1596 writer
.write("\u2561");
1598 if ((rowI
< rows
.size() - 1)
1599 && (cellI
== columns
.size() - 1)
1600 && (row
.bottomBorder
== Border
.THICK
)
1601 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1603 // Emit right tee (thick).
1604 writer
.write("\u2525");
1606 if ((rowI
== rows
.size() - 1)
1607 && (cellI
== columns
.size() - 1)
1608 && (row
.bottomBorder
== Border
.SINGLE
)
1609 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1611 // Emit right bottom corner.
1612 writer
.write("\u2518");
1614 if ((rowI
== rows
.size() - 1)
1615 && (cellI
== columns
.size() - 1)
1616 && (row
.bottomBorder
== Border
.DOUBLE
)
1617 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1619 // Emit right bottom corner (double).
1620 writer
.write("\u255b");
1622 if ((rowI
== rows
.size() - 1)
1623 && (cellI
== columns
.size() - 1)
1624 && (row
.bottomBorder
== Border
.THICK
)
1625 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1627 // Emit right bottom corner (thick).
1628 writer
.write("\u2519");
1630 if ((rowI
< rows
.size() - 1)
1631 && (cellI
< columns
.size() - 1)
1632 && (row
.bottomBorder
== Border
.SINGLE
)
1633 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1635 // Emit intersection.
1636 writer
.write("\u253c");
1638 if ((rowI
< rows
.size() - 1)
1639 && (cellI
< columns
.size() - 1)
1640 && (row
.bottomBorder
== Border
.DOUBLE
)
1641 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1643 // Emit intersection (double).
1644 writer
.write("\u256a");
1646 if ((rowI
< rows
.size() - 1)
1647 && (cellI
< columns
.size() - 1)
1648 && (row
.bottomBorder
== Border
.THICK
)
1649 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1651 // Emit intersection (thick).
1652 writer
.write("\u253f");
1654 if ((rowI
== rows
.size() - 1)
1655 && (cellI
< columns
.size() - 1)
1656 && (row
.bottomBorder
== Border
.SINGLE
)
1657 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1660 writer
.write("\u2534");
1662 if ((rowI
== rows
.size() - 1)
1663 && (cellI
< columns
.size() - 1)
1664 && (row
.bottomBorder
== Border
.DOUBLE
)
1665 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1667 // Emit bottom tee (double).
1668 writer
.write("\u2567");
1670 if ((rowI
== rows
.size() - 1)
1671 && (cellI
< columns
.size() - 1)
1672 && (row
.bottomBorder
== Border
.THICK
)
1673 && (columns
.get(cellI
).rightBorder
== Border
.SINGLE
)
1675 // Emit bottom tee (thick).
1676 writer
.write("\u2537");
1686 if (writer
!= null) {
1693 * Set the selected cell location.
1695 * @param column the selected cell location column
1696 * @param row the selected cell location row
1698 public void setSelectedCell(final int column
, final int row
) {
1699 if ((column
< 0) || (column
> columns
.size() - 1)) {
1700 throw new IndexOutOfBoundsException("Column count is " +
1701 columns
.size() + ", requested index " + column
);
1703 if ((row
< 0) || (row
> rows
.size() - 1)) {
1704 throw new IndexOutOfBoundsException("Row count is " +
1705 rows
.size() + ", requested index " + row
);
1707 selectedColumn
= column
;
1713 * Get a particular cell.
1715 * @param column the cell column
1716 * @param row the cell row
1719 public Cell
getCell(final int column
, final int row
) {
1720 if ((column
< 0) || (column
> columns
.size() - 1)) {
1721 throw new IndexOutOfBoundsException("Column count is " +
1722 columns
.size() + ", requested index " + column
);
1724 if ((row
< 0) || (row
> rows
.size() - 1)) {
1725 throw new IndexOutOfBoundsException("Row count is " +
1726 rows
.size() + ", requested index " + row
);
1728 return rows
.get(row
).get(column
);
1732 * Get the text of a particular cell.
1734 * @param column the cell column
1735 * @param row the cell row
1736 * @return the text in the cell
1738 public String
getCellText(final int column
, final int row
) {
1739 if ((column
< 0) || (column
> columns
.size() - 1)) {
1740 throw new IndexOutOfBoundsException("Column count is " +
1741 columns
.size() + ", requested index " + column
);
1743 if ((row
< 0) || (row
> rows
.size() - 1)) {
1744 throw new IndexOutOfBoundsException("Row count is " +
1745 rows
.size() + ", requested index " + row
);
1747 return rows
.get(row
).get(column
).getText();
1751 * Set the text of a particular cell.
1753 * @param column the cell column
1754 * @param row the cell row
1755 * @param text the text to put into the cell
1757 public void setCellText(final int column
, final int row
,
1758 final String text
) {
1760 if ((column
< 0) || (column
> columns
.size() - 1)) {
1761 throw new IndexOutOfBoundsException("Column count is " +
1762 columns
.size() + ", requested index " + column
);
1764 if ((row
< 0) || (row
> rows
.size() - 1)) {
1765 throw new IndexOutOfBoundsException("Row count is " +
1766 rows
.size() + ", requested index " + row
);
1768 rows
.get(row
).get(column
).setText(text
);
1772 * Set the action to perform when the user presses enter on a particular
1775 * @param column the cell column
1776 * @param row the cell row
1777 * @param action the action to perform when the user presses enter on the
1780 public void setCellEnterAction(final int column
, final int row
,
1781 final TAction action
) {
1783 if ((column
< 0) || (column
> columns
.size() - 1)) {
1784 throw new IndexOutOfBoundsException("Column count is " +
1785 columns
.size() + ", requested index " + column
);
1787 if ((row
< 0) || (row
> rows
.size() - 1)) {
1788 throw new IndexOutOfBoundsException("Row count is " +
1789 rows
.size() + ", requested index " + row
);
1791 rows
.get(row
).get(column
).field
.setEnterAction(action
);
1795 * Set the action to perform when the user updates a particular cell.
1797 * @param column the cell column
1798 * @param row the cell row
1799 * @param action the action to perform when the user updates the cell
1801 public void setCellUpdateAction(final int column
, final int row
,
1802 final TAction action
) {
1804 if ((column
< 0) || (column
> columns
.size() - 1)) {
1805 throw new IndexOutOfBoundsException("Column count is " +
1806 columns
.size() + ", requested index " + column
);
1808 if ((row
< 0) || (row
> rows
.size() - 1)) {
1809 throw new IndexOutOfBoundsException("Row count is " +
1810 rows
.size() + ", requested index " + row
);
1812 rows
.get(row
).get(column
).field
.setUpdateAction(action
);
1816 * Get the width of a column.
1818 * @param column the column number
1819 * @return the width of the column
1821 public int getColumnWidth(final int column
) {
1822 if ((column
< 0) || (column
> columns
.size() - 1)) {
1823 throw new IndexOutOfBoundsException("Column count is " +
1824 columns
.size() + ", requested index " + column
);
1826 return columns
.get(column
).width
;
1830 * Set the width of a column.
1832 * @param column the column number
1833 * @param width the new width of the column
1835 public void setColumnWidth(final int column
, final int width
) {
1836 if ((column
< 0) || (column
> columns
.size() - 1)) {
1837 throw new IndexOutOfBoundsException("Column count is " +
1838 columns
.size() + ", requested index " + column
);
1842 // Columns may not be smaller than 4 cells wide.
1846 int delta
= width
- columns
.get(column
).width
;
1847 columns
.get(column
).width
= width
;
1848 for (Cell cell
: columns
.get(column
).cells
) {
1849 cell
.setWidth(columns
.get(column
).width
);
1850 cell
.field
.setWidth(columns
.get(column
).width
);
1852 for (int i
= column
+ 1; i
< columns
.size(); i
++) {
1853 columns
.get(i
).setX(columns
.get(i
).getX() + delta
);
1855 if (column
== columns
.size() - 1) {
1856 bottomRightCorner();
1863 * Get the label of a column.
1865 * @param column the column number
1866 * @return the label of the column
1868 public String
getColumnLabel(final int column
) {
1869 if ((column
< 0) || (column
> columns
.size() - 1)) {
1870 throw new IndexOutOfBoundsException("Column count is " +
1871 columns
.size() + ", requested index " + column
);
1873 return columns
.get(column
).label
;
1877 * Set the label of a column.
1879 * @param column the column number
1880 * @param label the new label of the column
1882 public void setColumnLabel(final int column
, final String label
) {
1883 if ((column
< 0) || (column
> columns
.size() - 1)) {
1884 throw new IndexOutOfBoundsException("Column count is " +
1885 columns
.size() + ", requested index " + column
);
1887 columns
.get(column
).label
= label
;
1891 * Get the label of a row.
1893 * @param row the row number
1894 * @return the label of the row
1896 public String
getRowLabel(final int row
) {
1897 if ((row
< 0) || (row
> rows
.size() - 1)) {
1898 throw new IndexOutOfBoundsException("Row count is " +
1899 rows
.size() + ", requested index " + row
);
1901 return rows
.get(row
).label
;
1905 * Set the label of a row.
1907 * @param row the row number
1908 * @param label the new label of the row
1910 public void setRowLabel(final int row
, final String label
) {
1911 if ((row
< 0) || (row
> rows
.size() - 1)) {
1912 throw new IndexOutOfBoundsException("Row count is " +
1913 rows
.size() + ", requested index " + row
);
1915 rows
.get(row
).label
= label
;
1919 * Insert one row at a particular index.
1921 * @param idx the row number
1923 private void insertRowAt(final int idx
) {
1924 Row newRow
= new Row(idx
);
1925 for (int i
= 0; i
< columns
.size(); i
++) {
1926 Cell cell
= new Cell(this, columns
.get(i
).getX(),
1927 rows
.get(idx
).getY(), COLUMN_DEFAULT_WIDTH
, 1, i
, idx
);
1929 columns
.get(i
).cells
.add(idx
, cell
);
1931 rows
.add(idx
, newRow
);
1933 for (int x
= 0; x
< columns
.size(); x
++) {
1934 for (int y
= idx
; y
< rows
.size(); y
++) {
1935 columns
.get(x
).get(y
).row
= y
;
1936 columns
.get(x
).get(y
).column
= x
;
1939 for (int i
= idx
+ 1; i
< rows
.size(); i
++) {
1940 String oldRowLabel
= Integer
.toString(i
- 1);
1941 if (rows
.get(i
).label
.equals(oldRowLabel
)) {
1942 rows
.get(i
).label
= Integer
.toString(i
);
1949 * Insert one row above a particular row.
1951 * @param row the row number
1953 public void insertRowAbove(final int row
) {
1954 if ((row
< 0) || (row
> rows
.size() - 1)) {
1955 throw new IndexOutOfBoundsException("Row count is " +
1956 rows
.size() + ", requested index " + row
);
1960 activate(columns
.get(selectedColumn
).get(selectedRow
));
1964 * Insert one row below a particular row.
1966 * @param row the row number
1968 public void insertRowBelow(final int row
) {
1969 if ((row
< 0) || (row
> rows
.size() - 1)) {
1970 throw new IndexOutOfBoundsException("Row count is " +
1971 rows
.size() + ", requested index " + row
);
1974 if (idx
< rows
.size()) {
1976 activate(columns
.get(selectedColumn
).get(selectedRow
));
1980 // row is the last row, we need to perform an append.
1981 Row newRow
= new Row(idx
);
1982 for (int i
= 0; i
< columns
.size(); i
++) {
1983 Cell cell
= new Cell(this, columns
.get(i
).getX(),
1984 rows
.get(row
).getY(), COLUMN_DEFAULT_WIDTH
, 1, i
, idx
);
1986 columns
.get(i
).cells
.add(cell
);
1990 activate(columns
.get(selectedColumn
).get(selectedRow
));
1994 * Delete a particular row.
1996 * @param row the row number
1998 public void deleteRow(final int row
) {
1999 if ((row
< 0) || (row
> rows
.size() - 1)) {
2000 throw new IndexOutOfBoundsException("Row count is " +
2001 rows
.size() + ", requested index " + row
);
2003 if (rows
.size() == 1) {
2004 // Don't delete the last row.
2007 for (int i
= 0; i
< columns
.size(); i
++) {
2008 Cell cell
= columns
.get(i
).cells
.remove(row
);
2009 getChildren().remove(cell
);
2013 for (int x
= 0; x
< columns
.size(); x
++) {
2014 for (int y
= row
; y
< rows
.size(); y
++) {
2015 columns
.get(x
).get(y
).row
= y
;
2016 columns
.get(x
).get(y
).column
= x
;
2019 for (int i
= row
; i
< rows
.size(); i
++) {
2020 String oldRowLabel
= Integer
.toString(i
+ 1);
2021 if (rows
.get(i
).label
.equals(oldRowLabel
)) {
2022 rows
.get(i
).label
= Integer
.toString(i
);
2025 if (selectedRow
== rows
.size()) {
2028 activate(columns
.get(selectedColumn
).get(selectedRow
));
2029 bottomRightCorner();
2033 * Insert one column at a particular index.
2035 * @param idx the column number
2037 private void insertColumnAt(final int idx
) {
2038 Column newColumn
= new Column(idx
);
2039 for (int i
= 0; i
< rows
.size(); i
++) {
2040 Cell cell
= new Cell(this, columns
.get(idx
).getX(),
2041 rows
.get(i
).getY(), COLUMN_DEFAULT_WIDTH
, 1, idx
, i
);
2042 newColumn
.add(cell
);
2043 rows
.get(i
).cells
.add(idx
, cell
);
2045 columns
.add(idx
, newColumn
);
2047 for (int x
= idx
; x
< columns
.size(); x
++) {
2048 for (int y
= 0; y
< rows
.size(); y
++) {
2049 columns
.get(x
).get(y
).row
= y
;
2050 columns
.get(x
).get(y
).column
= x
;
2053 for (int i
= idx
+ 1; i
< columns
.size(); i
++) {
2054 String oldColumnLabel
= makeColumnLabel(i
- 1);
2055 if (columns
.get(i
).label
.equals(oldColumnLabel
)) {
2056 columns
.get(i
).label
= makeColumnLabel(i
);
2063 * Insert one column to the left of a particular column.
2065 * @param column the column number
2067 public void insertColumnLeft(final int column
) {
2068 if ((column
< 0) || (column
> columns
.size() - 1)) {
2069 throw new IndexOutOfBoundsException("Column count is " +
2070 columns
.size() + ", requested index " + column
);
2072 insertColumnAt(column
);
2074 activate(columns
.get(selectedColumn
).get(selectedRow
));
2078 * Insert one column to the right of a particular column.
2080 * @param column the column number
2082 public void insertColumnRight(final int column
) {
2083 if ((column
< 0) || (column
> columns
.size() - 1)) {
2084 throw new IndexOutOfBoundsException("Column count is " +
2085 columns
.size() + ", requested index " + column
);
2087 int idx
= column
+ 1;
2088 if (idx
< columns
.size()) {
2089 insertColumnAt(idx
);
2090 activate(columns
.get(selectedColumn
).get(selectedRow
));
2094 // column is the last column, we need to perform an append.
2095 Column newColumn
= new Column(idx
);
2096 for (int i
= 0; i
< rows
.size(); i
++) {
2097 Cell cell
= new Cell(this, columns
.get(column
).getX(),
2098 rows
.get(i
).getY(), COLUMN_DEFAULT_WIDTH
, 1, idx
, i
);
2099 newColumn
.add(cell
);
2100 rows
.get(i
).cells
.add(cell
);
2102 columns
.add(newColumn
);
2104 activate(columns
.get(selectedColumn
).get(selectedRow
));
2108 * Delete a particular column.
2110 * @param column the column number
2112 public void deleteColumn(final int column
) {
2113 if ((column
< 0) || (column
> columns
.size() - 1)) {
2114 throw new IndexOutOfBoundsException("Column count is " +
2115 columns
.size() + ", requested index " + column
);
2117 if (columns
.size() == 1) {
2118 // Don't delete the last column.
2121 for (int i
= 0; i
< rows
.size(); i
++) {
2122 Cell cell
= rows
.get(i
).cells
.remove(column
);
2123 getChildren().remove(cell
);
2125 columns
.remove(column
);
2127 for (int x
= column
; x
< columns
.size(); x
++) {
2128 for (int y
= 0; y
< rows
.size(); y
++) {
2129 columns
.get(x
).get(y
).row
= y
;
2130 columns
.get(x
).get(y
).column
= x
;
2133 for (int i
= column
; i
< columns
.size(); i
++) {
2134 String oldColumnLabel
= makeColumnLabel(i
+ 1);
2135 if (columns
.get(i
).label
.equals(oldColumnLabel
)) {
2136 columns
.get(i
).label
= makeColumnLabel(i
);
2139 if (selectedColumn
== columns
.size()) {
2142 activate(columns
.get(selectedColumn
).get(selectedRow
));
2143 bottomRightCorner();
2147 * Delete the selected cell, shifting cells over to the left.
2149 public void deleteCellShiftLeft() {
2150 // All we do is copy the text from every cell in this row over.
2151 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
2152 setCellText(i
- 1, selectedRow
, getCellText(i
, selectedRow
));
2154 setCellText(columns
.size() - 1, selectedRow
, "");
2158 * Delete the selected cell, shifting cells from below up.
2160 public void deleteCellShiftUp() {
2161 // All we do is copy the text from every cell in this column up.
2162 for (int i
= selectedRow
+ 1; i
< rows
.size(); i
++) {
2163 setCellText(selectedColumn
, i
- 1, getCellText(selectedColumn
, i
));
2165 setCellText(selectedColumn
, rows
.size() - 1, "");
2169 * Set a particular cell read-only (non-editable) or not.
2171 * @param column the cell column
2172 * @param row the cell row
2173 * @param readOnly if true, the cell will be non-editable
2175 public void setCellReadOnly(final int column
, final int row
,
2176 final boolean readOnly
) {
2178 if ((column
< 0) || (column
> columns
.size() - 1)) {
2179 throw new IndexOutOfBoundsException("Column count is " +
2180 columns
.size() + ", requested index " + column
);
2182 if ((row
< 0) || (row
> rows
.size() - 1)) {
2183 throw new IndexOutOfBoundsException("Row count is " +
2184 rows
.size() + ", requested index " + row
);
2186 rows
.get(row
).get(column
).setReadOnly(readOnly
);
2190 * Set an entire row of cells read-only (non-editable) or not.
2192 * @param row the row number
2193 * @param readOnly if true, the cells will be non-editable
2195 public void setRowReadOnly(final int row
, final boolean readOnly
) {
2196 if ((row
< 0) || (row
> rows
.size() - 1)) {
2197 throw new IndexOutOfBoundsException("Row count is " +
2198 rows
.size() + ", requested index " + row
);
2200 for (Cell cell
: rows
.get(row
).cells
) {
2201 cell
.setReadOnly(readOnly
);
2206 * Set an entire column of cells read-only (non-editable) or not.
2208 * @param column the column number
2209 * @param readOnly if true, the cells will be non-editable
2211 public void setColumnReadOnly(final int column
, final boolean readOnly
) {
2212 if ((column
< 0) || (column
> columns
.size() - 1)) {
2213 throw new IndexOutOfBoundsException("Column count is " +
2214 columns
.size() + ", requested index " + column
);
2216 for (Cell cell
: columns
.get(column
).cells
) {
2217 cell
.setReadOnly(readOnly
);
2222 * Set all borders across the entire table to Border.NONE.
2224 public void setBorderAllNone() {
2225 topBorder
= Border
.NONE
;
2226 leftBorder
= Border
.NONE
;
2227 for (int i
= 0; i
< columns
.size(); i
++) {
2228 columns
.get(i
).rightBorder
= Border
.NONE
;
2230 for (int i
= 0; i
< rows
.size(); i
++) {
2231 rows
.get(i
).bottomBorder
= Border
.NONE
;
2232 rows
.get(i
).height
= 1;
2234 bottomRightCorner();
2238 * Set all borders across the entire table to Border.SINGLE.
2240 public void setBorderAllSingle() {
2241 topBorder
= Border
.SINGLE
;
2242 leftBorder
= Border
.SINGLE
;
2243 for (int i
= 0; i
< columns
.size(); i
++) {
2244 columns
.get(i
).rightBorder
= Border
.SINGLE
;
2246 for (int i
= 0; i
< rows
.size(); i
++) {
2247 rows
.get(i
).bottomBorder
= Border
.SINGLE
;
2248 rows
.get(i
).height
= 2;
2254 * Set all borders around the selected cell to Border.NONE.
2256 public void setBorderCellNone() {
2257 if (selectedRow
== 0) {
2258 topBorder
= Border
.NONE
;
2260 if (selectedColumn
== 0) {
2261 leftBorder
= Border
.NONE
;
2263 if (selectedColumn
> 0) {
2264 columns
.get(selectedColumn
- 1).rightBorder
= Border
.NONE
;
2266 columns
.get(selectedColumn
).rightBorder
= Border
.NONE
;
2267 if (selectedRow
> 0) {
2268 rows
.get(selectedRow
- 1).bottomBorder
= Border
.NONE
;
2269 rows
.get(selectedRow
- 1).height
= 1;
2271 rows
.get(selectedRow
).bottomBorder
= Border
.NONE
;
2272 rows
.get(selectedRow
).height
= 1;
2273 bottomRightCorner();
2277 * Set all borders around the selected cell to Border.SINGLE.
2279 public void setBorderCellSingle() {
2280 if (selectedRow
== 0) {
2281 topBorder
= Border
.SINGLE
;
2283 if (selectedColumn
== 0) {
2284 leftBorder
= Border
.SINGLE
;
2286 if (selectedColumn
> 0) {
2287 columns
.get(selectedColumn
- 1).rightBorder
= Border
.SINGLE
;
2289 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
2290 if (selectedRow
> 0) {
2291 rows
.get(selectedRow
- 1).bottomBorder
= Border
.SINGLE
;
2292 rows
.get(selectedRow
- 1).height
= 2;
2294 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
2295 rows
.get(selectedRow
).height
= 2;
2300 * Set the column border to the right of the selected cell to
2303 public void setBorderColumnRightSingle() {
2304 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
2309 * Set the column border to the right of the selected cell to
2312 public void setBorderColumnLeftSingle() {
2313 if (selectedColumn
== 0) {
2314 leftBorder
= Border
.SINGLE
;
2316 columns
.get(selectedColumn
- 1).rightBorder
= Border
.SINGLE
;
2322 * Set the row border above the selected cell to Border.SINGLE.
2324 public void setBorderRowAboveSingle() {
2325 if (selectedRow
== 0) {
2326 topBorder
= Border
.SINGLE
;
2328 rows
.get(selectedRow
- 1).bottomBorder
= Border
.SINGLE
;
2329 rows
.get(selectedRow
- 1).height
= 2;
2335 * Set the row border below the selected cell to Border.SINGLE.
2337 public void setBorderRowBelowSingle() {
2338 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
2339 rows
.get(selectedRow
).height
= 2;
2344 * Set the row border below the selected cell to Border.DOUBLE.
2346 public void setBorderRowBelowDouble() {
2347 rows
.get(selectedRow
).bottomBorder
= Border
.DOUBLE
;
2348 rows
.get(selectedRow
).height
= 2;
2353 * Set the row border below the selected cell to Border.THICK.
2355 public void setBorderRowBelowThick() {
2356 rows
.get(selectedRow
).bottomBorder
= Border
.THICK
;
2357 rows
.get(selectedRow
).height
= 2;