2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.io
.IOException
;
32 import java
.util
.ArrayList
;
33 import java
.util
.List
;
34 import java
.util
.ResourceBundle
;
36 import jexer
.bits
.CellAttributes
;
37 import jexer
.event
.TKeypressEvent
;
38 import jexer
.event
.TMenuEvent
;
39 import jexer
.event
.TMouseEvent
;
40 import jexer
.event
.TResizeEvent
;
41 import jexer
.menu
.TMenu
;
42 import static jexer
.TKeypress
.*;
45 * TTableWidget is used to display and edit regular two-dimensional tables of
48 * This class was inspired by a TTable implementation originally developed by
49 * David "Niki" ROULET [niki@nikiroo.be], made available under MIT at
50 * https://github.com/nikiroo/jexer/tree/ttable_pull.
52 public class TTableWidget
extends TWidget
{
57 private static final ResourceBundle i18n
= ResourceBundle
.getBundle(TTableWidget
.class.getName());
59 // ------------------------------------------------------------------------
60 // Constants --------------------------------------------------------------
61 // ------------------------------------------------------------------------
64 * Available borders for cells.
73 * Single bar: \u2502 (vertical) and \u2500 (horizontal).
78 * Double bar: \u2551 (vertical) and \u2550 (horizontal).
83 * Thick bar: \u2503 (vertical heavy) and \u2501 (horizontal heavy).
91 private static final int ROW_LABEL_WIDTH
= 8;
94 * Column label height.
96 private static final int COLUMN_LABEL_HEIGHT
= 1;
99 * Column default width.
101 private static final int COLUMN_DEFAULT_WIDTH
= 8;
106 private static final int EXTRA_ROWS
= 10;
109 * Extra columns to add.
111 private static final int EXTRA_COLUMNS
= 10 * (8 + 1);
113 // ------------------------------------------------------------------------
114 // Variables --------------------------------------------------------------
115 // ------------------------------------------------------------------------
118 * The underlying data, organized as columns.
120 private ArrayList
<Column
> columns
= new ArrayList
<Column
>();
123 * The underlying data, organized as rows.
125 private ArrayList
<Row
> rows
= new ArrayList
<Row
>();
128 * The row in model corresponding to the top-left visible cell.
133 * The column in model corresponding to the top-left visible cell.
135 private int left
= 0;
138 * The row in model corresponding to the currently selected cell.
140 private int selectedRow
= 0;
143 * The column in model corresponding to the currently selected cell.
145 private int selectedColumn
= 0;
148 * If true, highlight the entire row of the currently-selected cell.
150 private boolean highlightRow
= false;
153 * If true, highlight the entire column of the currently-selected cell.
155 private boolean highlightColumn
= false;
158 * If true, show the row labels as the first column.
160 private boolean showRowLabels
= true;
163 * If true, show the column labels as the first row.
165 private boolean showColumnLabels
= true;
168 * The top border for the first row.
170 private Border topBorder
= Border
.NONE
;
173 * The left border for the first column.
175 private Border leftBorder
= Border
.NONE
;
178 * Column represents a column of cells.
180 public class Column
{
183 * X position of this column.
190 private int width
= COLUMN_DEFAULT_WIDTH
;
193 * The cells of this column.
195 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
200 private String label
= "";
203 * The right border for this column.
205 private Border rightBorder
= Border
.NONE
;
208 * Constructor sets label to lettered column.
210 * @param col column number to use for this column. Column 0 will be
211 * "A", column 1 will be "B", column 26 will be "AA", and so on.
214 label
= makeColumnLabel(col
);
218 * Add an entry to this column.
220 * @param cell the cell to add
222 public void add(final Cell cell
) {
227 * Get an entry from this column.
229 * @param row the entry index to get
230 * @return the cell at row
232 public Cell
get(final int row
) {
233 return cells
.get(row
);
237 * Get the X position of the cells in this column.
239 * @return the position
246 * Set the X position of the cells in this column.
248 * @param x the position
250 public void setX(final int x
) {
251 for (Cell cell
: cells
) {
260 * Row represents a row of cells.
265 * Y position of this row.
272 private int height
= 1;
275 * The cells of this row.
277 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
282 private String label
= "";
285 * The bottom border for this row.
287 private Border bottomBorder
= Border
.NONE
;
290 * Constructor sets label to numbered row.
292 * @param row row number to use for this row
295 label
= Integer
.toString(row
);
299 * Add an entry to this column.
301 * @param cell the cell to add
303 public void add(final Cell cell
) {
308 * Get an entry from this row.
310 * @param column the entry index to get
311 * @return the cell at column
313 public Cell
get(final int column
) {
314 return cells
.get(column
);
317 * Get the Y position of the cells in this column.
319 * @return the position
326 * Set the Y position of the cells in this column.
328 * @param y the position
330 public void setY(final int y
) {
331 for (Cell cell
: cells
) {
340 * Cell represents an editable cell in the table. Normally, navigation
341 * to a cell only highlights it; pressing Enter or F2 will switch to
344 public class Cell
extends TWidget
{
346 // --------------------------------------------------------------------
347 // Variables ----------------------------------------------------------
348 // --------------------------------------------------------------------
351 * The field containing the cell's data.
353 private TField field
;
356 * The column of this cell.
361 * The row of this cell.
366 * If true, the cell is being edited.
368 private boolean isEditing
= false;
371 * If true, the cell is read-only (non-editable).
373 private boolean readOnly
= false;
376 * Text of field before editing.
378 private String fieldText
;
380 // --------------------------------------------------------------------
381 // Constructors -------------------------------------------------------
382 // --------------------------------------------------------------------
385 * Public constructor.
387 * @param parent parent widget
388 * @param x column relative to parent
389 * @param y row relative to parent
390 * @param width width of widget
391 * @param height height of widget
392 * @param column column index of this cell
393 * @param row row index of this cell
395 public Cell(final TTableWidget parent
, final int x
, final int y
,
396 final int width
, final int height
, final int column
,
399 super(parent
, x
, y
, width
, height
);
400 this.column
= column
;
403 field
= addField(0, 0, width
, false);
404 field
.setEnabled(false);
405 field
.setBackgroundChar(' ');
408 // --------------------------------------------------------------------
409 // Event handlers -----------------------------------------------------
410 // --------------------------------------------------------------------
413 * Handle mouse double-click events.
415 * @param mouse mouse double-click event
418 public void onMouseDoubleClick(final TMouseEvent mouse
) {
419 // Use TWidget's code to pass the event to the children.
420 super.onMouseDown(mouse
);
422 // Double-click means to start editing.
423 fieldText
= field
.getText();
425 field
.setEnabled(true);
429 // Let the table know that I was activated.
430 ((TTableWidget
) getParent()).selectedRow
= row
;
431 ((TTableWidget
) getParent()).selectedColumn
= column
;
432 ((TTableWidget
) getParent()).alignGrid();
437 * Handle mouse press events.
439 * @param mouse mouse button press event
442 public void onMouseDown(final TMouseEvent mouse
) {
443 // Use TWidget's code to pass the event to the children.
444 super.onMouseDown(mouse
);
447 // Let the table know that I was activated.
448 ((TTableWidget
) getParent()).selectedRow
= row
;
449 ((TTableWidget
) getParent()).selectedColumn
= column
;
450 ((TTableWidget
) getParent()).alignGrid();
455 * Handle mouse release events.
457 * @param mouse mouse button release event
460 public void onMouseUp(final TMouseEvent mouse
) {
461 // Use TWidget's code to pass the event to the children.
462 super.onMouseDown(mouse
);
465 // Let the table know that I was activated.
466 ((TTableWidget
) getParent()).selectedRow
= row
;
467 ((TTableWidget
) getParent()).selectedColumn
= column
;
468 ((TTableWidget
) getParent()).alignGrid();
475 * @param keypress keystroke event
478 public void onKeypress(final TKeypressEvent keypress
) {
479 // System.err.println("Cell onKeypress: " + keypress);
482 // Read only: do nothing.
487 if (keypress
.equals(kbEsc
)) {
488 // ESC cancels the edit.
492 if (keypress
.equals(kbEnter
)) {
493 // Enter ends editing.
495 // Pass down to field first so that it can execute
496 // enterAction if specified.
497 super.onKeypress(keypress
);
499 fieldText
= field
.getText();
501 field
.setEnabled(false);
504 // Pass down to field.
505 super.onKeypress(keypress
);
508 if (keypress
.equals(kbEnter
) || keypress
.equals(kbF2
)) {
509 // Enter or F2 starts editing.
510 fieldText
= field
.getText();
512 field
.setEnabled(true);
518 // --------------------------------------------------------------------
519 // TWidget ------------------------------------------------------------
520 // --------------------------------------------------------------------
527 TTableWidget table
= (TTableWidget
) getParent();
529 if (isAbsoluteActive()) {
531 field
.setActiveColorKey("tfield.active");
532 field
.setInactiveColorKey("tfield.inactive");
534 field
.setActiveColorKey("ttable.selected");
535 field
.setInactiveColorKey("ttable.selected");
537 } else if (((table
.selectedColumn
== column
)
538 && ((table
.selectedRow
== row
)
539 || (table
.highlightColumn
== true)))
540 || ((table
.selectedRow
== row
)
541 && ((table
.selectedColumn
== column
)
542 || (table
.highlightRow
== true)))
544 field
.setActiveColorKey("ttable.active");
545 field
.setInactiveColorKey("ttable.active");
547 field
.setActiveColorKey("ttable.active");
548 field
.setInactiveColorKey("ttable.inactive");
551 assert (isVisible() == true);
556 // --------------------------------------------------------------------
557 // TTable.Cell --------------------------------------------------------
558 // --------------------------------------------------------------------
565 public final String
getText() {
566 return field
.getText();
572 * @param text the new field text
574 public void setText(final String text
) {
579 * Cancel any pending edit.
581 public void cancelEdit() {
582 // Cancel any pending edit.
583 if (fieldText
!= null) {
584 field
.setText(fieldText
);
587 field
.setEnabled(false);
591 * Set an entire column of cells read-only (non-editable) or not.
593 * @param readOnly if true, the cells will be non-editable
595 public void setReadOnly(final boolean readOnly
) {
597 this.readOnly
= readOnly
;
602 // ------------------------------------------------------------------------
603 // Constructors -----------------------------------------------------------
604 // ------------------------------------------------------------------------
607 * Public constructor.
609 * @param parent parent widget
610 * @param x column relative to parent
611 * @param y row relative to parent
612 * @param width width of widget
613 * @param height height of widget
615 public TTableWidget(final TWidget parent
, final int x
, final int y
,
616 final int width
, final int height
) {
618 super(parent
, x
, y
, width
, height
);
620 // Initialize the starting row and column.
621 rows
.add(new Row(0));
622 columns
.add(new Column(0));
624 // Place a grid of cells that fit in this space.
626 for (int i
= 0; i
< height
+ EXTRA_ROWS
; i
+= rows
.get(0).height
) {
628 for (int j
= 0; j
< width
+ EXTRA_COLUMNS
;
629 j
+= columns
.get(0).width
) {
631 Cell cell
= new Cell(this, 0, 0, /* j, i, */ columns
.get(0).width
,
632 rows
.get(0).height
, column
, row
);
634 // DEBUG: set a grid of cell index labels
636 cell
.setText("" + row
+ " " + column
);
637 rows
.get(row
).add(cell
);
638 columns
.get(column
).add(cell
);
640 (j
+ columns
.get(0).width
< width
+ EXTRA_COLUMNS
)
642 columns
.add(new Column(column
+ 1));
646 if (i
+ rows
.get(0).height
< height
+ EXTRA_ROWS
) {
647 rows
.add(new Row(row
+ 1));
651 for (int i
= 0; i
< rows
.size(); i
++) {
652 rows
.get(i
).setY(i
+ (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0));
654 for (int j
= 0; j
< columns
.size(); j
++) {
655 columns
.get(j
).setX((j
* COLUMN_DEFAULT_WIDTH
) +
656 (showRowLabels ? ROW_LABEL_WIDTH
: 0));
658 activate(columns
.get(selectedColumn
).get(selectedRow
));
662 // Set the menu to match the flags.
663 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
).
664 setChecked(showRowLabels
);
665 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
).
666 setChecked(showColumnLabels
);
667 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
).
668 setChecked(highlightRow
);
669 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
).
670 setChecked(highlightColumn
);
675 // ------------------------------------------------------------------------
676 // Event handlers ---------------------------------------------------------
677 // ------------------------------------------------------------------------
680 * Handle mouse press events.
682 * @param mouse mouse button press event
685 public void onMouseDown(final TMouseEvent mouse
) {
686 if (mouse
.isMouseWheelUp() || mouse
.isMouseWheelDown()) {
687 // Treat wheel up/down as 3 up/down
688 TKeypressEvent keyEvent
;
689 if (mouse
.isMouseWheelUp()) {
690 keyEvent
= new TKeypressEvent(kbUp
);
692 keyEvent
= new TKeypressEvent(kbDown
);
694 for (int i
= 0; i
< 3; i
++) {
695 onKeypress(keyEvent
);
700 // Use TWidget's code to pass the event to the children.
701 super.onMouseDown(mouse
);
707 * @param keypress keystroke event
710 public void onKeypress(final TKeypressEvent keypress
) {
711 if (keypress
.equals(kbTab
)
712 || keypress
.equals(kbShiftTab
)
714 // Squash tab and back-tab. They don't make sense in the TTable
719 // If editing, pass to that cell and do nothing else.
720 if (getSelectedCell().isEditing
) {
721 super.onKeypress(keypress
);
725 if (keypress
.equals(kbLeft
)) {
727 if (selectedColumn
> 0) {
730 activate(columns
.get(selectedColumn
).get(selectedRow
));
731 } else if (keypress
.equals(kbRight
)) {
733 if (selectedColumn
< columns
.size() - 1) {
736 activate(columns
.get(selectedColumn
).get(selectedRow
));
737 } else if (keypress
.equals(kbUp
)) {
739 if (selectedRow
> 0) {
742 activate(columns
.get(selectedColumn
).get(selectedRow
));
743 } else if (keypress
.equals(kbDown
)) {
745 if (selectedRow
< rows
.size() - 1) {
748 activate(columns
.get(selectedColumn
).get(selectedRow
));
749 } else if (keypress
.equals(kbHome
)) {
750 // Home - leftmost column
752 activate(columns
.get(selectedColumn
).get(selectedRow
));
753 } else if (keypress
.equals(kbEnd
)) {
754 // End - rightmost column
755 selectedColumn
= columns
.size() - 1;
756 activate(columns
.get(selectedColumn
).get(selectedRow
));
757 } else if (keypress
.equals(kbPgUp
)) {
758 // PgUp - Treat like multiple up
759 for (int i
= 0; i
< getHeight() - 2; i
++) {
760 if (selectedRow
> 0) {
764 activate(columns
.get(selectedColumn
).get(selectedRow
));
765 } else if (keypress
.equals(kbPgDn
)) {
766 // PgDn - Treat like multiple up
767 for (int i
= 0; i
< getHeight() - 2; i
++) {
768 if (selectedRow
< rows
.size() - 1) {
772 activate(columns
.get(selectedColumn
).get(selectedRow
));
773 } else if (keypress
.equals(kbCtrlHome
)) {
774 // Ctrl-Home - go to top-left
777 activate(columns
.get(selectedColumn
).get(selectedRow
));
778 activate(columns
.get(selectedColumn
).get(selectedRow
));
779 } else if (keypress
.equals(kbCtrlEnd
)) {
780 // Ctrl-End - go to bottom-right
781 selectedRow
= rows
.size() - 1;
782 selectedColumn
= columns
.size() - 1;
783 activate(columns
.get(selectedColumn
).get(selectedRow
));
784 activate(columns
.get(selectedColumn
).get(selectedRow
));
787 super.onKeypress(keypress
);
790 // We may have scrolled off screen. Reset positions as needed to
791 // make the newly selected cell visible.
796 * Handle widget resize events.
798 * @param event resize event
801 public void onResize(final TResizeEvent event
) {
802 super.onResize(event
);
808 * Handle posted menu events.
810 * @param menu menu event
813 public void onMenu(final TMenuEvent menu
) {
816 switch (menu
.getId()) {
817 case TMenu
.MID_TABLE_RENAME_COLUMN
:
818 inputBox
= inputBox(i18n
.getString("renameColumnInputTitle"),
819 i18n
.getString("renameColumnInputCaption"),
820 getColumnLabel(selectedColumn
), TMessageBox
.Type
.OKCANCEL
);
821 if (inputBox
.isOk()) {
822 setColumnLabel(selectedColumn
, inputBox
.getText());
825 case TMenu
.MID_TABLE_RENAME_ROW
:
826 inputBox
= inputBox(i18n
.getString("renameRowInputTitle"),
827 i18n
.getString("renameRowInputCaption"),
828 getRowLabel(selectedRow
), TMessageBox
.Type
.OKCANCEL
);
829 if (inputBox
.isOk()) {
830 setRowLabel(selectedRow
, inputBox
.getText());
833 case TMenu
.MID_TABLE_VIEW_ROW_LABELS
:
834 showRowLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
836 case TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
:
837 showColumnLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
839 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
:
840 highlightRow
= getApplication().getMenuItem(menu
.getId()).getChecked();
842 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
:
843 highlightColumn
= getApplication().getMenuItem(menu
.getId()).getChecked();
845 case TMenu
.MID_TABLE_BORDER_NONE
:
846 topBorder
= Border
.NONE
;
847 leftBorder
= Border
.NONE
;
848 for (int i
= 0; i
< columns
.size(); i
++) {
849 columns
.get(i
).rightBorder
= Border
.NONE
;
851 for (int i
= 0; i
< rows
.size(); i
++) {
852 rows
.get(i
).bottomBorder
= Border
.NONE
;
853 rows
.get(i
).height
= 1;
856 case TMenu
.MID_TABLE_BORDER_ALL
:
857 topBorder
= Border
.SINGLE
;
858 leftBorder
= Border
.SINGLE
;
859 for (int i
= 0; i
< columns
.size(); i
++) {
860 columns
.get(i
).rightBorder
= Border
.SINGLE
;
862 for (int i
= 0; i
< rows
.size(); i
++) {
863 rows
.get(i
).bottomBorder
= Border
.SINGLE
;
864 rows
.get(i
).height
= 2;
867 case TMenu
.MID_TABLE_BORDER_CELL_NONE
:
868 if (selectedRow
== 0) {
869 topBorder
= Border
.NONE
;
871 if (selectedColumn
== 0) {
872 leftBorder
= Border
.NONE
;
874 columns
.get(selectedColumn
).rightBorder
= Border
.NONE
;
875 rows
.get(selectedRow
).bottomBorder
= Border
.NONE
;
876 rows
.get(selectedRow
).height
= 1;
878 case TMenu
.MID_TABLE_BORDER_CELL_ALL
:
879 if (selectedRow
== 0) {
880 topBorder
= Border
.SINGLE
;
882 if (selectedColumn
== 0) {
883 leftBorder
= Border
.SINGLE
;
885 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
886 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
887 rows
.get(selectedRow
).height
= 2;
889 case TMenu
.MID_TABLE_BORDER_RIGHT
:
890 columns
.get(selectedColumn
).rightBorder
= Border
.SINGLE
;
892 case TMenu
.MID_TABLE_BORDER_LEFT
:
893 if (selectedColumn
== 0) {
894 leftBorder
= Border
.SINGLE
;
896 columns
.get(selectedColumn
- 1).rightBorder
= Border
.SINGLE
;
899 case TMenu
.MID_TABLE_BORDER_TOP
:
900 if (selectedRow
== 0) {
901 topBorder
= Border
.SINGLE
;
903 rows
.get(selectedRow
- 1).bottomBorder
= Border
.SINGLE
;
904 rows
.get(selectedRow
- 1).height
= 2;
907 case TMenu
.MID_TABLE_BORDER_BOTTOM
:
908 rows
.get(selectedRow
).bottomBorder
= Border
.SINGLE
;
909 rows
.get(selectedRow
).height
= 2;
911 case TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
:
912 rows
.get(selectedRow
).bottomBorder
= Border
.DOUBLE
;
913 rows
.get(selectedRow
).height
= 2;
915 case TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
:
916 rows
.get(selectedRow
).bottomBorder
= Border
.THICK
;
917 rows
.get(selectedRow
).height
= 2;
919 case TMenu
.MID_TABLE_DELETE_LEFT
:
920 deleteCellShiftLeft();
921 activate(columns
.get(selectedColumn
).get(selectedRow
));
923 case TMenu
.MID_TABLE_DELETE_UP
:
925 activate(columns
.get(selectedColumn
).get(selectedRow
));
927 case TMenu
.MID_TABLE_DELETE_ROW
:
928 deleteRow(selectedRow
);
929 activate(columns
.get(selectedColumn
).get(selectedRow
));
931 case TMenu
.MID_TABLE_DELETE_COLUMN
:
932 deleteColumn(selectedColumn
);
933 activate(columns
.get(selectedColumn
).get(selectedRow
));
935 case TMenu
.MID_TABLE_INSERT_LEFT
:
936 insertColumnLeft(selectedColumn
);
937 activate(columns
.get(selectedColumn
).get(selectedRow
));
939 case TMenu
.MID_TABLE_INSERT_RIGHT
:
940 insertColumnRight(selectedColumn
);
941 activate(columns
.get(selectedColumn
).get(selectedRow
));
943 case TMenu
.MID_TABLE_INSERT_ABOVE
:
944 insertRowAbove(selectedColumn
);
945 activate(columns
.get(selectedColumn
).get(selectedRow
));
947 case TMenu
.MID_TABLE_INSERT_BELOW
:
948 insertRowBelow(selectedColumn
);
949 activate(columns
.get(selectedColumn
).get(selectedRow
));
951 case TMenu
.MID_TABLE_COLUMN_NARROW
:
952 columns
.get(selectedColumn
).width
--;
953 for (Cell cell
: getSelectedColumn().cells
) {
954 cell
.setWidth(columns
.get(selectedColumn
).width
);
955 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
957 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
958 columns
.get(i
).setX(columns
.get(i
).getX() - 1);
961 case TMenu
.MID_TABLE_COLUMN_WIDEN
:
962 columns
.get(selectedColumn
).width
++;
963 for (Cell cell
: getSelectedColumn().cells
) {
964 cell
.setWidth(columns
.get(selectedColumn
).width
);
965 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
967 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
968 columns
.get(i
).setX(columns
.get(i
).getX() + 1);
971 case TMenu
.MID_TABLE_FILE_SAVE_CSV
:
974 case TMenu
.MID_TABLE_FILE_SAVE_TEXT
:
981 // Fix/redraw the display.
985 // ------------------------------------------------------------------------
986 // TWidget ----------------------------------------------------------------
987 // ------------------------------------------------------------------------
990 * Draw the table row/column labels, and borders.
994 CellAttributes labelColor
= getTheme().getColor("ttable.label");
995 CellAttributes labelColorSelected
= getTheme().getColor("ttable.label.selected");
996 CellAttributes borderColor
= getTheme().getColor("ttable.border");
999 if (showColumnLabels
== true) {
1000 for (int i
= left
; i
< columns
.size(); i
++) {
1001 if (columns
.get(i
).get(top
).isVisible() == false) {
1004 putStringXY(columns
.get(i
).get(top
).getX(), 0,
1005 String
.format(" %-" +
1006 (columns
.get(i
).width
- 2)
1007 + "s ", columns
.get(i
).label
),
1008 (i
== selectedColumn ? labelColorSelected
: labelColor
));
1013 if (showRowLabels
== true) {
1014 for (int i
= top
; i
< rows
.size(); i
++) {
1015 if (rows
.get(i
).get(left
).isVisible() == false) {
1018 putStringXY(0, rows
.get(i
).get(left
).getY(),
1019 String
.format(" %-6s ", rows
.get(i
).label
),
1020 (i
== selectedRow ? labelColorSelected
: labelColor
));
1024 // Draw vertical borders.
1025 if (leftBorder
== Border
.SINGLE
) {
1026 vLineXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1027 (topBorder
== Border
.NONE ?
0 : 1) +
1028 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
1029 getHeight(), '\u2502', borderColor
);
1031 for (int i
= left
; i
< columns
.size(); i
++) {
1032 if (columns
.get(i
).get(top
).isVisible() == false) {
1035 if (columns
.get(i
).rightBorder
== Border
.SINGLE
) {
1036 vLineXY(columns
.get(i
).getX() + columns
.get(i
).width
,
1037 (topBorder
== Border
.NONE ?
0 : 1) +
1038 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
1039 getHeight(), '\u2502', borderColor
);
1043 // Draw horizontal borders.
1044 if (topBorder
== Border
.SINGLE
) {
1045 hLineXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1046 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
1047 getWidth(), '\u2500', borderColor
);
1049 for (int i
= top
; i
< rows
.size(); i
++) {
1050 if (rows
.get(i
).get(left
).isVisible() == false) {
1053 if (rows
.get(i
).bottomBorder
== Border
.SINGLE
) {
1054 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
1055 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
1056 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1057 getWidth(), '\u2500', borderColor
);
1058 } else if (rows
.get(i
).bottomBorder
== Border
.DOUBLE
) {
1059 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
1060 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
1061 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1062 getWidth(), '\u2550', borderColor
);
1063 } else if (rows
.get(i
).bottomBorder
== Border
.THICK
) {
1064 hLineXY((leftBorder
== Border
.NONE ?
0 : 1) +
1065 (showRowLabels ? ROW_LABEL_WIDTH
: 0),
1066 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1067 getWidth(), '\u2501', borderColor
);
1070 // Top-left corner if needed
1071 if ((topBorder
== Border
.SINGLE
) && (leftBorder
== Border
.SINGLE
)) {
1072 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1073 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
1074 '\u250c', borderColor
);
1077 // Now draw the correct corners
1078 for (int i
= top
; i
< rows
.size(); i
++) {
1079 if (rows
.get(i
).get(left
).isVisible() == false) {
1082 for (int j
= left
; j
< columns
.size(); j
++) {
1083 if (columns
.get(j
).get(i
).isVisible() == false) {
1086 if ((i
== top
) && (topBorder
== Border
.SINGLE
)
1087 && (columns
.get(j
).rightBorder
== Border
.SINGLE
)
1090 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
1091 (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0),
1092 '\u252c', borderColor
);
1094 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
1095 && (rows
.get(i
).bottomBorder
== Border
.SINGLE
)
1098 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1099 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1100 '\u251c', borderColor
);
1102 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
1103 && (rows
.get(i
).bottomBorder
== Border
.SINGLE
)
1105 // Intersection of single bars
1106 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
1107 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1108 '\u253c', borderColor
);
1110 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
1111 && (rows
.get(i
).bottomBorder
== Border
.DOUBLE
)
1113 // Left tee: single bar vertical, double bar horizontal
1114 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1115 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1116 '\u255e', borderColor
);
1118 if ((j
== left
) && (leftBorder
== Border
.SINGLE
)
1119 && (rows
.get(i
).bottomBorder
== Border
.THICK
)
1121 // Left tee: single bar vertical, thick bar horizontal
1122 putCharXY((showRowLabels ? ROW_LABEL_WIDTH
: 0),
1123 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1124 '\u251d', borderColor
);
1126 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
1127 && (rows
.get(i
).bottomBorder
== Border
.DOUBLE
)
1129 // Intersection: single bar vertical, double bar
1131 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
1132 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1133 '\u256a', borderColor
);
1135 if ((columns
.get(j
).rightBorder
== Border
.SINGLE
)
1136 && (rows
.get(i
).bottomBorder
== Border
.THICK
)
1138 // Intersection: single bar vertical, thick bar
1140 putCharXY(columns
.get(j
).getX() + columns
.get(j
).width
,
1141 rows
.get(i
).getY() + rows
.get(i
).height
- 1,
1142 '\u253f', borderColor
);
1147 // Now draw the window borders.
1151 // ------------------------------------------------------------------------
1152 // TTable -----------------------------------------------------------------
1153 // ------------------------------------------------------------------------
1156 * Generate the default letter name for a column number.
1158 * @param col column number to use for this column. Column 0 will be
1159 * "A", column 1 will be "B", column 26 will be "AA", and so on.
1161 private String
makeColumnLabel(int col
) {
1162 StringBuilder sb
= new StringBuilder();
1164 sb
.append((char) ('A' + (col
% 26)));
1170 return sb
.reverse().toString();
1174 * Get the currently-selected cell.
1176 * @return the selected cell
1178 public Cell
getSelectedCell() {
1179 assert (rows
.get(selectedRow
) != null);
1180 assert (rows
.get(selectedRow
).get(selectedColumn
) != null);
1181 assert (columns
.get(selectedColumn
) != null);
1182 assert (columns
.get(selectedColumn
).get(selectedRow
) != null);
1183 assert (rows
.get(selectedRow
).get(selectedColumn
) ==
1184 columns
.get(selectedColumn
).get(selectedRow
));
1186 return (columns
.get(selectedColumn
).get(selectedRow
));
1190 * Get the currently-selected column.
1192 * @return the selected column
1194 public Column
getSelectedColumn() {
1195 assert (selectedColumn
>= 0);
1196 assert (columns
.size() > selectedColumn
);
1197 assert (columns
.get(selectedColumn
) != null);
1198 return columns
.get(selectedColumn
);
1202 * Get the currently-selected row.
1204 * @return the selected row
1206 public Row
getSelectedRow() {
1207 assert (selectedRow
>= 0);
1208 assert (rows
.size() > selectedRow
);
1209 assert (rows
.get(selectedRow
) != null);
1210 return rows
.get(selectedRow
);
1214 * Get the currently-selected column number. 0 is the left-most column.
1216 * @return the selected column number
1218 public int getSelectedColumnNumber() {
1219 return selectedColumn
;
1223 * Set the currently-selected column number. 0 is the left-most column.
1225 * @param column the column number to select
1227 public void setSelectedColumnNumber(final int column
) {
1228 if ((column
< 0) || (column
> columns
.size() - 1)) {
1229 throw new IndexOutOfBoundsException("Column count is " +
1230 columns
.size() + ", requested index " + column
);
1232 selectedColumn
= column
;
1233 activate(columns
.get(selectedColumn
).get(selectedRow
));
1238 * Get the currently-selected row number. 0 is the top-most row.
1240 * @return the selected row number
1242 public int getSelectedRowNumber() {
1247 * Set the currently-selected row number. 0 is the left-most column.
1249 * @param row the row number to select
1251 public void setSelectedRowNumber(final int row
) {
1252 if ((row
< 0) || (row
> rows
.size() - 1)) {
1253 throw new IndexOutOfBoundsException("Row count is " +
1254 rows
.size() + ", requested index " + row
);
1257 activate(columns
.get(selectedColumn
).get(selectedRow
));
1262 * Get the number of columns.
1264 * @return the number of columns
1266 public int getColumnCount() {
1267 return columns
.size();
1271 * Get the number of rows.
1273 * @return the number of rows
1275 public int getRowCount() {
1281 * Push top and left to the bottom-most right corner of the available
1284 private void bottomRightCorner() {
1285 int viewColumns
= getWidth();
1286 if (showRowLabels
== true) {
1287 viewColumns
-= ROW_LABEL_WIDTH
;
1290 // Set left and top such that the table stays on screen if possible.
1291 top
= rows
.size() - getHeight();
1292 left
= columns
.size() - (getWidth() / (viewColumns
/ (COLUMN_DEFAULT_WIDTH
+ 1)));
1293 // Now ensure the selection is visible.
1298 * Align the grid so that the selected cell is fully visible.
1300 private void alignGrid() {
1301 int viewColumns
= getWidth();
1302 if (showRowLabels
== true) {
1303 viewColumns
-= ROW_LABEL_WIDTH
;
1305 if (leftBorder
!= Border
.NONE
) {
1308 int viewRows
= getHeight();
1309 if (showColumnLabels
== true) {
1310 viewRows
-= COLUMN_LABEL_HEIGHT
;
1312 if (topBorder
!= Border
.NONE
) {
1316 // If we pushed left or right, adjust the box to include the new
1318 if (selectedColumn
< left
) {
1319 left
= selectedColumn
- 1;
1324 if (selectedRow
< top
) {
1325 top
= selectedRow
- 1;
1332 * viewColumns and viewRows now contain the available columns and
1333 * rows available to view the selected cell. We adjust left and top
1334 * to ensure the selected cell is within view, and then make all
1335 * cells outside the box between (left, top) and (right, bottom)
1338 * We need to calculate right and bottom now.
1342 boolean done
= false;
1344 int rightCellX
= (showRowLabels ? ROW_LABEL_WIDTH
: 0);
1345 if (leftBorder
!= Border
.NONE
) {
1348 int maxCellX
= rightCellX
+ viewColumns
;
1350 boolean selectedIsVisible
= false;
1352 for (int x
= left
; x
< columns
.size(); x
++) {
1353 if (x
== selectedColumn
) {
1354 selectedX
= rightCellX
;
1355 if (selectedX
+ columns
.get(x
).width
+ 1 <= maxCellX
) {
1356 selectedIsVisible
= true;
1359 rightCellX
+= columns
.get(x
).width
+ 1;
1360 if (rightCellX
>= maxCellX
) {
1365 if (right
< selectedColumn
) {
1366 // selectedColumn is outside the view range. Push left over,
1367 // and calculate again.
1369 } else if (left
== selectedColumn
) {
1370 // selectedColumn doesn't fit inside the view range, but we
1371 // can't go over any further either. Bail out.
1373 } else if (selectedIsVisible
== false) {
1374 // selectedColumn doesn't fit inside the view range, continue
1378 // selectedColumn is fully visible, all done.
1379 assert (selectedIsVisible
== true);
1385 // We have the left/right range correct, set cell visibility and
1386 // column X positions.
1387 int leftCellX
= showRowLabels ? ROW_LABEL_WIDTH
: 0;
1388 if (leftBorder
!= Border
.NONE
) {
1391 for (int x
= 0; x
< columns
.size(); x
++) {
1392 if ((x
< left
) || (x
> right
)) {
1393 for (int i
= 0; i
< rows
.size(); i
++) {
1394 columns
.get(x
).get(i
).setVisible(false);
1395 columns
.get(x
).setX(getWidth() + 1);
1399 for (int i
= 0; i
< rows
.size(); i
++) {
1400 columns
.get(x
).get(i
).setVisible(true);
1402 columns
.get(x
).setX(leftCellX
);
1403 leftCellX
+= columns
.get(x
).width
+ 1;
1410 int bottomCellY
= (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0);
1411 if (topBorder
!= Border
.NONE
) {
1414 int maxCellY
= bottomCellY
+ viewRows
;
1416 for (int y
= top
; y
< rows
.size(); y
++) {
1417 bottomCellY
+= rows
.get(y
).height
;
1418 if (bottomCellY
>= maxCellY
) {
1423 if (bottom
< selectedRow
) {
1424 // selectedRow is outside the view range. Push top down, and
1428 // selectedRow is inside the view range, done.
1433 // We have the top/bottom range correct, set cell visibility and
1435 int topCellY
= showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0;
1436 if (topBorder
!= Border
.NONE
) {
1439 for (int y
= 0; y
< rows
.size(); y
++) {
1440 if ((y
< top
) || (y
> bottom
)) {
1441 for (int i
= 0; i
< columns
.size(); i
++) {
1442 rows
.get(y
).get(i
).setVisible(false);
1444 rows
.get(y
).setY(getHeight() + 1);
1447 for (int i
= 0; i
< columns
.size(); i
++) {
1448 rows
.get(y
).get(i
).setVisible(true);
1450 rows
.get(y
).setY(topCellY
);
1451 topCellY
+= rows
.get(y
).height
;
1454 // Last thing: cancel any edits that are not the selected cell.
1455 for (int y
= 0; y
< rows
.size(); y
++) {
1456 for (int x
= 0; x
< columns
.size(); x
++) {
1457 if ((x
== selectedColumn
) && (y
== selectedRow
)) {
1460 rows
.get(y
).get(x
).cancelEdit();
1466 * Save contents to file.
1468 * @param filename file to save to
1469 * @throws IOException if a java.io operation throws
1471 public void saveToFilename(final String filename
) throws IOException
{
1476 * Set the selected cell location.
1478 * @param column the selected cell location column
1479 * @param row the selected cell location row
1481 public void setSelectedCell(final int column
, final int row
) {
1482 if ((column
< 0) || (column
> columns
.size() - 1)) {
1483 throw new IndexOutOfBoundsException("Column count is " +
1484 columns
.size() + ", requested index " + column
);
1486 if ((row
< 0) || (row
> rows
.size() - 1)) {
1487 throw new IndexOutOfBoundsException("Row count is " +
1488 rows
.size() + ", requested index " + row
);
1490 selectedColumn
= column
;
1496 * Get a particular cell.
1498 * @param column the cell column
1499 * @param row the cell row
1502 public Cell
getCell(final int column
, final int row
) {
1503 if ((column
< 0) || (column
> columns
.size() - 1)) {
1504 throw new IndexOutOfBoundsException("Column count is " +
1505 columns
.size() + ", requested index " + column
);
1507 if ((row
< 0) || (row
> rows
.size() - 1)) {
1508 throw new IndexOutOfBoundsException("Row count is " +
1509 rows
.size() + ", requested index " + row
);
1511 return rows
.get(row
).get(column
);
1515 * Get the text of a particular cell.
1517 * @param column the cell column
1518 * @param row the cell row
1519 * @return the text in the cell
1521 public String
getCellText(final int column
, final int row
) {
1522 if ((column
< 0) || (column
> columns
.size() - 1)) {
1523 throw new IndexOutOfBoundsException("Column count is " +
1524 columns
.size() + ", requested index " + column
);
1526 if ((row
< 0) || (row
> rows
.size() - 1)) {
1527 throw new IndexOutOfBoundsException("Row count is " +
1528 rows
.size() + ", requested index " + row
);
1530 return rows
.get(row
).get(column
).getText();
1534 * Set the text of a particular cell.
1536 * @param column the cell column
1537 * @param row the cell row
1538 * @param text the text to put into the cell
1540 public void setCellText(final int column
, final int row
,
1541 final String text
) {
1543 if ((column
< 0) || (column
> columns
.size() - 1)) {
1544 throw new IndexOutOfBoundsException("Column count is " +
1545 columns
.size() + ", requested index " + column
);
1547 if ((row
< 0) || (row
> rows
.size() - 1)) {
1548 throw new IndexOutOfBoundsException("Row count is " +
1549 rows
.size() + ", requested index " + row
);
1551 rows
.get(row
).get(column
).setText(text
);
1555 * Set the action to perform when the user presses enter on a particular
1558 * @param column the cell column
1559 * @param row the cell row
1560 * @param action the action to perform when the user presses enter on the
1563 public void setCellEnterAction(final int column
, final int row
,
1564 final TAction action
) {
1566 if ((column
< 0) || (column
> columns
.size() - 1)) {
1567 throw new IndexOutOfBoundsException("Column count is " +
1568 columns
.size() + ", requested index " + column
);
1570 if ((row
< 0) || (row
> rows
.size() - 1)) {
1571 throw new IndexOutOfBoundsException("Row count is " +
1572 rows
.size() + ", requested index " + row
);
1574 rows
.get(row
).get(column
).field
.setEnterAction(action
);
1578 * Set the action to perform when the user updates a particular cell.
1580 * @param column the cell column
1581 * @param row the cell row
1582 * @param action the action to perform when the user updates the cell
1584 public void setCellUpdateAction(final int column
, final int row
,
1585 final TAction action
) {
1587 if ((column
< 0) || (column
> columns
.size() - 1)) {
1588 throw new IndexOutOfBoundsException("Column count is " +
1589 columns
.size() + ", requested index " + column
);
1591 if ((row
< 0) || (row
> rows
.size() - 1)) {
1592 throw new IndexOutOfBoundsException("Row count is " +
1593 rows
.size() + ", requested index " + row
);
1595 rows
.get(row
).get(column
).field
.setUpdateAction(action
);
1599 * Get the width of a column.
1601 * @param column the column number
1602 * @return the width of the column
1604 public int getColumnWidth(final int column
) {
1605 if ((column
< 0) || (column
> columns
.size() - 1)) {
1606 throw new IndexOutOfBoundsException("Column count is " +
1607 columns
.size() + ", requested index " + column
);
1609 return columns
.get(column
).width
;
1613 * Set the width of a column.
1615 * @param column the column number
1616 * @param width the new width of the column
1618 public void setColumnWidth(final int column
, final int width
) {
1619 if ((column
< 0) || (column
> columns
.size() - 1)) {
1620 throw new IndexOutOfBoundsException("Column count is " +
1621 columns
.size() + ", requested index " + column
);
1623 columns
.get(column
).width
= width
;
1627 * Get the label of a column.
1629 * @param column the column number
1630 * @return the label of the column
1632 public String
getColumnLabel(final int column
) {
1633 if ((column
< 0) || (column
> columns
.size() - 1)) {
1634 throw new IndexOutOfBoundsException("Column count is " +
1635 columns
.size() + ", requested index " + column
);
1637 return columns
.get(column
).label
;
1641 * Set the label of a column.
1643 * @param column the column number
1644 * @param label the new label of the column
1646 public void setColumnLabel(final int column
, final String label
) {
1647 if ((column
< 0) || (column
> columns
.size() - 1)) {
1648 throw new IndexOutOfBoundsException("Column count is " +
1649 columns
.size() + ", requested index " + column
);
1651 columns
.get(column
).label
= label
;
1655 * Get the label of a row.
1657 * @param row the row number
1658 * @return the label of the row
1660 public String
getRowLabel(final int row
) {
1661 if ((row
< 0) || (row
> rows
.size() - 1)) {
1662 throw new IndexOutOfBoundsException("Row count is " +
1663 rows
.size() + ", requested index " + row
);
1665 return rows
.get(row
).label
;
1669 * Set the label of a row.
1671 * @param row the row number
1672 * @param label the new label of the row
1674 public void setRowLabel(final int row
, final String label
) {
1675 if ((row
< 0) || (row
> rows
.size() - 1)) {
1676 throw new IndexOutOfBoundsException("Row count is " +
1677 rows
.size() + ", requested index " + row
);
1679 rows
.get(row
).label
= label
;
1683 * Insert one row at a particular index.
1685 * @param idx the row number
1687 private void insertRowAt(final int idx
) {
1688 Row newRow
= new Row(idx
);
1689 for (int i
= 0; i
< columns
.size(); i
++) {
1690 Cell cell
= new Cell(this, columns
.get(i
).getX(),
1691 rows
.get(selectedRow
).getY(), COLUMN_DEFAULT_WIDTH
, 1, i
, idx
);
1693 columns
.get(i
).cells
.add(idx
, cell
);
1695 rows
.add(idx
, newRow
);
1697 for (int x
= 0; x
< columns
.size(); x
++) {
1698 for (int y
= idx
; y
< rows
.size(); y
++) {
1699 columns
.get(x
).get(y
).row
= y
;
1700 columns
.get(x
).get(y
).column
= x
;
1703 for (int i
= idx
+ 1; i
< rows
.size(); i
++) {
1704 String oldRowLabel
= Integer
.toString(i
- 1);
1705 if (rows
.get(i
).label
.equals(oldRowLabel
)) {
1706 rows
.get(i
).label
= Integer
.toString(i
);
1713 * Insert one row above a particular row.
1715 * @param row the row number
1717 public void insertRowAbove(final int row
) {
1718 if ((row
< 0) || (row
> rows
.size() - 1)) {
1719 throw new IndexOutOfBoundsException("Row count is " +
1720 rows
.size() + ", requested index " + row
);
1722 insertRowAt(selectedRow
);
1727 * Insert one row below a particular row.
1729 * @param row the row number
1731 public void insertRowBelow(final int row
) {
1732 if ((row
< 0) || (row
> rows
.size() - 1)) {
1733 throw new IndexOutOfBoundsException("Row count is " +
1734 rows
.size() + ", requested index " + row
);
1736 int idx
= selectedRow
+ 1;
1737 if (idx
< rows
.size()) {
1742 // selectedRow is the last row, we need to perform an append.
1743 Row newRow
= new Row(idx
);
1744 for (int i
= 0; i
< columns
.size(); i
++) {
1745 Cell cell
= new Cell(this, columns
.get(i
).getX(),
1746 rows
.get(selectedRow
).getY(), COLUMN_DEFAULT_WIDTH
, 1, i
, idx
);
1748 columns
.get(i
).cells
.add(cell
);
1755 * Delete a particular row.
1757 * @param row the row number
1759 public void deleteRow(final int row
) {
1760 if ((row
< 0) || (row
> rows
.size() - 1)) {
1761 throw new IndexOutOfBoundsException("Row count is " +
1762 rows
.size() + ", requested index " + row
);
1764 if (rows
.size() == 1) {
1765 // Don't delete the last row.
1768 for (int i
= 0; i
< columns
.size(); i
++) {
1769 Cell cell
= columns
.get(i
).cells
.remove(row
);
1770 getChildren().remove(cell
);
1774 for (int x
= 0; x
< columns
.size(); x
++) {
1775 for (int y
= row
; y
< rows
.size(); y
++) {
1776 columns
.get(x
).get(y
).row
= y
;
1777 columns
.get(x
).get(y
).column
= x
;
1780 for (int i
= row
; i
< rows
.size(); i
++) {
1781 String oldRowLabel
= Integer
.toString(i
+ 1);
1782 if (rows
.get(i
).label
.equals(oldRowLabel
)) {
1783 rows
.get(i
).label
= Integer
.toString(i
);
1786 if (selectedRow
== rows
.size()) {
1789 bottomRightCorner();
1793 * Insert one column at a particular index.
1795 * @param idx the column number
1797 private void insertColumnAt(final int idx
) {
1798 Column newColumn
= new Column(idx
);
1799 for (int i
= 0; i
< rows
.size(); i
++) {
1800 Cell cell
= new Cell(this, columns
.get(selectedColumn
).getX(),
1801 rows
.get(i
).getY(), COLUMN_DEFAULT_WIDTH
, 1, idx
, i
);
1802 newColumn
.add(cell
);
1803 rows
.get(i
).cells
.add(idx
, cell
);
1805 columns
.add(idx
, newColumn
);
1807 for (int x
= idx
; x
< columns
.size(); x
++) {
1808 for (int y
= 0; y
< rows
.size(); y
++) {
1809 columns
.get(x
).get(y
).row
= y
;
1810 columns
.get(x
).get(y
).column
= x
;
1813 for (int i
= idx
+ 1; i
< columns
.size(); i
++) {
1814 String oldColumnLabel
= makeColumnLabel(i
- 1);
1815 if (columns
.get(i
).label
.equals(oldColumnLabel
)) {
1816 columns
.get(i
).label
= makeColumnLabel(i
);
1823 * Insert one column to the left of a particular column.
1825 * @param column the column number
1827 public void insertColumnLeft(final int column
) {
1828 if ((column
< 0) || (column
> columns
.size() - 1)) {
1829 throw new IndexOutOfBoundsException("Column count is " +
1830 columns
.size() + ", requested index " + column
);
1832 insertColumnAt(selectedColumn
);
1837 * Insert one column to the right of a particular column.
1839 * @param column the column number
1841 public void insertColumnRight(final int column
) {
1842 if ((column
< 0) || (column
> columns
.size() - 1)) {
1843 throw new IndexOutOfBoundsException("Column count is " +
1844 columns
.size() + ", requested index " + column
);
1846 int idx
= selectedColumn
+ 1;
1847 if (idx
< columns
.size()) {
1848 insertColumnAt(idx
);
1852 // selectedColumn is the last column, we need to perform an append.
1853 Column newColumn
= new Column(idx
);
1854 for (int i
= 0; i
< rows
.size(); i
++) {
1855 Cell cell
= new Cell(this, columns
.get(selectedColumn
).getX(),
1856 rows
.get(i
).getY(), COLUMN_DEFAULT_WIDTH
, 1, idx
, i
);
1857 newColumn
.add(cell
);
1858 rows
.get(i
).cells
.add(cell
);
1860 columns
.add(newColumn
);
1865 * Delete a particular column.
1867 * @param column the column number
1869 public void deleteColumn(final int column
) {
1870 if ((column
< 0) || (column
> columns
.size() - 1)) {
1871 throw new IndexOutOfBoundsException("Column count is " +
1872 columns
.size() + ", requested index " + column
);
1874 if (columns
.size() == 1) {
1875 // Don't delete the last column.
1878 for (int i
= 0; i
< rows
.size(); i
++) {
1879 Cell cell
= rows
.get(i
).cells
.remove(column
);
1880 getChildren().remove(cell
);
1882 columns
.remove(column
);
1884 for (int x
= column
; x
< columns
.size(); x
++) {
1885 for (int y
= 0; y
< rows
.size(); y
++) {
1886 columns
.get(x
).get(y
).row
= y
;
1887 columns
.get(x
).get(y
).column
= x
;
1890 for (int i
= column
; i
< columns
.size(); i
++) {
1891 String oldColumnLabel
= makeColumnLabel(i
+ 1);
1892 if (columns
.get(i
).label
.equals(oldColumnLabel
)) {
1893 columns
.get(i
).label
= makeColumnLabel(i
);
1896 if (selectedColumn
== columns
.size()) {
1899 bottomRightCorner();
1903 * Delete the selected cell, shifting cells over to the left.
1905 public void deleteCellShiftLeft() {
1906 // All we do is copy the text from every cell in this row over.
1907 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
1908 setCellText(i
- 1, selectedRow
, getCellText(i
, selectedRow
));
1910 setCellText(columns
.size() - 1, selectedRow
, "");
1914 * Delete the selected cell, shifting cells from below up.
1916 public void deleteCellShiftUp() {
1917 // All we do is copy the text from every cell in this column up.
1918 for (int i
= selectedRow
+ 1; i
< rows
.size(); i
++) {
1919 setCellText(selectedColumn
, i
- 1, getCellText(selectedColumn
, i
));
1921 setCellText(selectedColumn
, rows
.size() - 1, "");
1925 * Set a particular cell read-only (non-editable) or not.
1927 * @param column the cell column
1928 * @param row the cell row
1929 * @param readOnly if true, the cell will be non-editable
1931 public void setCellReadOnly(final int column
, final int row
,
1932 final boolean readOnly
) {
1934 if ((column
< 0) || (column
> columns
.size() - 1)) {
1935 throw new IndexOutOfBoundsException("Column count is " +
1936 columns
.size() + ", requested index " + column
);
1938 if ((row
< 0) || (row
> rows
.size() - 1)) {
1939 throw new IndexOutOfBoundsException("Row count is " +
1940 rows
.size() + ", requested index " + row
);
1942 rows
.get(row
).get(column
).setReadOnly(readOnly
);
1946 * Set an entire row of cells read-only (non-editable) or not.
1948 * @param row the row number
1949 * @param readOnly if true, the cells will be non-editable
1951 public void setRowReadOnly(final int row
, final boolean readOnly
) {
1952 if ((row
< 0) || (row
> rows
.size() - 1)) {
1953 throw new IndexOutOfBoundsException("Row count is " +
1954 rows
.size() + ", requested index " + row
);
1956 for (Cell cell
: rows
.get(row
).cells
) {
1957 cell
.setReadOnly(readOnly
);
1962 * Set an entire column of cells read-only (non-editable) or not.
1964 * @param column the column number
1965 * @param readOnly if true, the cells will be non-editable
1967 public void setColumnReadOnly(final int column
, final boolean readOnly
) {
1968 if ((column
< 0) || (column
> columns
.size() - 1)) {
1969 throw new IndexOutOfBoundsException("Column count is " +
1970 columns
.size() + ", requested index " + column
);
1972 for (Cell cell
: columns
.get(column
).cells
) {
1973 cell
.setReadOnly(readOnly
);