2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.io
.IOException
;
32 import java
.util
.ArrayList
;
33 import java
.util
.List
;
35 import jexer
.bits
.CellAttributes
;
36 import jexer
.event
.TKeypressEvent
;
37 import jexer
.event
.TMenuEvent
;
38 import jexer
.event
.TMouseEvent
;
39 import jexer
.event
.TResizeEvent
;
40 import jexer
.menu
.TMenu
;
41 import static jexer
.TKeypress
.*;
44 * TTableWidget is used to display and edit regular two-dimensional tables of
47 * This class was inspired by a TTable implementation originally developed by
48 * David "Niki" ROULET [niki@nikiroo.be], made available under MIT at
49 * https://github.com/nikiroo/jexer/tree/ttable_pull.
51 public class TTableWidget
extends TWidget
{
53 // ------------------------------------------------------------------------
54 // Constants --------------------------------------------------------------
55 // ------------------------------------------------------------------------
58 * Available borders for cells.
67 * Single bar: \u2502 (vertical) and \u2500 (horizontal).
72 * Double bar: \u2551 (vertical) and \u2550 (horizontal).
77 * Thick bar: \u258C (vertical, left half block) and \u2580
78 * (horizontal, upper block).
86 private static final int ROW_LABEL_WIDTH
= 8;
89 * Column label height.
91 private static final int COLUMN_LABEL_HEIGHT
= 1;
96 private static final int EXTRA_ROWS
= 10;
99 * Extra columns to add.
101 private static final int EXTRA_COLUMNS
= 10 * (8 + 1);
103 // ------------------------------------------------------------------------
104 // Variables --------------------------------------------------------------
105 // ------------------------------------------------------------------------
108 * The underlying data, organized as columns.
110 private ArrayList
<Column
> columns
= new ArrayList
<Column
>();
113 * The underlying data, organized as rows.
115 private ArrayList
<Row
> rows
= new ArrayList
<Row
>();
118 * The row in model corresponding to the top-left visible cell.
123 * The column in model corresponding to the top-left visible cell.
125 private int left
= 0;
128 * The row in model corresponding to the currently selected cell.
130 private int selectedRow
= 0;
133 * The column in model corresponding to the currently selected cell.
135 private int selectedColumn
= 0;
138 * If true, highlight the entire row of the currently-selected cell.
140 private boolean highlightRow
= false;
143 * If true, highlight the entire column of the currently-selected cell.
145 private boolean highlightColumn
= false;
148 * If true, show the row labels as the first column.
150 private boolean showRowLabels
= true;
153 * If true, show the column labels as the first row.
155 private boolean showColumnLabels
= true;
158 * The top border for the first row.
160 private Border topBorder
= Border
.NONE
;
163 * The left border for the first column.
165 private Border leftBorder
= Border
.NONE
;
168 * Column represents a column of cells.
170 public class Column
{
175 private int width
= 8;
178 * The cells of this column.
180 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
185 private String label
= "";
188 * The right border for this column.
190 private Border rightBorder
= Border
.NONE
;
193 * Constructor sets label to lettered column.
195 * @param col column number to use for this column. Column 0 will be
196 * "A", column 1 will be "B", column 26 will be "AA", and so on.
199 StringBuilder sb
= new StringBuilder();
201 sb
.append((char) ('A' + (col
% 26)));
207 label
= sb
.reverse().toString();
211 * Add an entry to this column.
213 * @param cell the cell to add
215 public void add(final Cell cell
) {
220 * Get an entry from this column.
222 * @param row the entry index to get
223 * @return the cell at row
225 public Cell
get(final int row
) {
226 return cells
.get(row
);
231 * Row represents a row of cells.
238 private int height
= 1;
241 * The cells of this row.
243 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
248 private String label
= "";
251 * The bottom border for this row.
253 private Border bottomBorder
= Border
.NONE
;
256 * Constructor sets label to numbered row.
258 * @param row row number to use for this row
261 label
= Integer
.toString(row
);
265 * Add an entry to this column.
267 * @param cell the cell to add
269 public void add(final Cell cell
) {
274 * Get an entry from this row.
276 * @param column the entry index to get
277 * @return the cell at column
279 public Cell
get(final int column
) {
280 return cells
.get(column
);
286 * Cell represents an editable cell in the table. Normally, navigation
287 * to a cell only highlights it; pressing Enter or F2 will switch to
290 public class Cell
extends TWidget
{
292 // --------------------------------------------------------------------
293 // Variables ----------------------------------------------------------
294 // --------------------------------------------------------------------
297 * The field containing the cell's data.
299 private TField field
;
302 * The column of this cell.
307 * The row of this cell.
312 * If true, the cell is being edited.
314 private boolean isEditing
= false;
317 * Text of field before editing.
319 private String fieldText
;
321 // --------------------------------------------------------------------
322 // Constructors -------------------------------------------------------
323 // --------------------------------------------------------------------
326 * Public constructor.
328 * @param parent parent widget
329 * @param x column relative to parent
330 * @param y row relative to parent
331 * @param width width of widget
332 * @param height height of widget
333 * @param column column index of this cell
334 * @param row row index of this cell
336 public Cell(final TTableWidget parent
, final int x
, final int y
,
337 final int width
, final int height
, final int column
,
340 super(parent
, x
, y
, width
, height
);
341 this.column
= column
;
344 field
= addField(0, 0, width
, false);
345 field
.setEnabled(false);
346 field
.setBackgroundChar(' ');
349 // --------------------------------------------------------------------
350 // Event handlers -----------------------------------------------------
351 // --------------------------------------------------------------------
354 * Handle mouse double-click events.
356 * @param mouse mouse double-click event
359 public void onMouseDoubleClick(final TMouseEvent mouse
) {
360 // Use TWidget's code to pass the event to the children.
361 super.onMouseDown(mouse
);
363 // Double-click means to start editing.
364 fieldText
= field
.getText();
366 field
.setEnabled(true);
370 // Let the table know that I was activated.
371 ((TTableWidget
) getParent()).selectedRow
= row
;
372 ((TTableWidget
) getParent()).selectedColumn
= column
;
373 ((TTableWidget
) getParent()).alignGrid(false);
378 * Handle mouse press events.
380 * @param mouse mouse button press event
383 public void onMouseDown(final TMouseEvent mouse
) {
384 // Use TWidget's code to pass the event to the children.
385 super.onMouseDown(mouse
);
388 // Let the table know that I was activated.
389 ((TTableWidget
) getParent()).selectedRow
= row
;
390 ((TTableWidget
) getParent()).selectedColumn
= column
;
391 ((TTableWidget
) getParent()).alignGrid(false);
396 * Handle mouse release events.
398 * @param mouse mouse button release event
401 public void onMouseUp(final TMouseEvent mouse
) {
402 // Use TWidget's code to pass the event to the children.
403 super.onMouseDown(mouse
);
406 // Let the table know that I was activated.
407 ((TTableWidget
) getParent()).selectedRow
= row
;
408 ((TTableWidget
) getParent()).selectedColumn
= column
;
409 ((TTableWidget
) getParent()).alignGrid(false);
416 * @param keypress keystroke event
419 public void onKeypress(final TKeypressEvent keypress
) {
420 // System.err.println("Cell onKeypress: " + keypress);
423 if (keypress
.equals(kbEsc
)) {
424 // ESC cancels the edit.
425 field
.setText(fieldText
);
427 field
.setEnabled(false);
430 if (keypress
.equals(kbEnter
)) {
431 // Enter ends editing.
432 fieldText
= field
.getText();
434 field
.setEnabled(false);
437 // Pass down to field.
438 super.onKeypress(keypress
);
441 if (keypress
.equals(kbEnter
) || keypress
.equals(kbF2
)) {
442 // Enter or F2 starts editing.
443 fieldText
= field
.getText();
445 field
.setEnabled(true);
451 // --------------------------------------------------------------------
452 // TWidget ------------------------------------------------------------
453 // --------------------------------------------------------------------
460 TTableWidget table
= (TTableWidget
) getParent();
462 if (isAbsoluteActive()) {
464 field
.setActiveColorKey("tfield.active");
465 field
.setInactiveColorKey("tfield.inactive");
467 field
.setActiveColorKey("ttable.selected");
468 field
.setInactiveColorKey("ttable.selected");
470 } else if (((table
.selectedColumn
== column
)
471 && ((table
.selectedRow
== row
)
472 || (table
.highlightColumn
== true)))
473 || ((table
.selectedRow
== row
)
474 && ((table
.selectedColumn
== column
)
475 || (table
.highlightRow
== true)))
477 field
.setActiveColorKey("ttable.active");
478 field
.setInactiveColorKey("ttable.active");
480 field
.setActiveColorKey("ttable.active");
481 field
.setInactiveColorKey("ttable.inactive");
484 assert (isVisible() == true);
489 // --------------------------------------------------------------------
490 // TTable.Cell --------------------------------------------------------
491 // --------------------------------------------------------------------
498 public final String
getText() {
499 return field
.getText();
505 * @param text the new field text
507 public void setText(final String text
) {
513 // ------------------------------------------------------------------------
514 // Constructors -----------------------------------------------------------
515 // ------------------------------------------------------------------------
518 * Public constructor.
520 * @param parent parent widget
521 * @param x column relative to parent
522 * @param y row relative to parent
523 * @param width width of widget
524 * @param height height of widget
526 public TTableWidget(final TWidget parent
, final int x
, final int y
,
527 final int width
, final int height
) {
529 super(parent
, x
, y
, width
, height
);
531 // Initialize the starting row and column.
532 rows
.add(new Row(0));
533 columns
.add(new Column(0));
535 // Place a grid of cells that fit in this space.
537 for (int i
= 0; i
< height
+ EXTRA_ROWS
; i
+= rows
.get(0).height
) {
539 for (int j
= 0; j
< width
+ EXTRA_COLUMNS
;
540 j
+= columns
.get(0).width
) {
542 Cell cell
= new Cell(this, j
, i
, columns
.get(0).width
,
543 rows
.get(0).height
, column
, row
);
545 cell
.setText("" + row
+ " " + column
);
546 rows
.get(row
).add(cell
);
547 columns
.get(column
).add(cell
);
549 (j
+ columns
.get(0).width
< width
+ EXTRA_COLUMNS
)
551 columns
.add(new Column(column
+ 1));
555 if (i
+ rows
.get(0).height
< height
+ EXTRA_ROWS
) {
556 rows
.add(new Row(row
+ 1));
560 activate(columns
.get(selectedColumn
).get(selectedRow
));
564 // Set the menu to match the flags.
565 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
).
566 setChecked(showRowLabels
);
567 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
).
568 setChecked(showColumnLabels
);
569 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
).
570 setChecked(highlightRow
);
571 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
).
572 setChecked(highlightColumn
);
577 // ------------------------------------------------------------------------
578 // Event handlers ---------------------------------------------------------
579 // ------------------------------------------------------------------------
582 * Handle mouse press events.
584 * @param mouse mouse button press event
587 public void onMouseDown(final TMouseEvent mouse
) {
588 if (mouse
.isMouseWheelUp() || mouse
.isMouseWheelDown()) {
589 // Treat wheel up/down as 3 up/down
590 TKeypressEvent keyEvent
;
591 if (mouse
.isMouseWheelUp()) {
592 keyEvent
= new TKeypressEvent(kbUp
);
594 keyEvent
= new TKeypressEvent(kbDown
);
596 for (int i
= 0; i
< 3; i
++) {
597 onKeypress(keyEvent
);
602 // Use TWidget's code to pass the event to the children.
603 super.onMouseDown(mouse
);
609 * @param keypress keystroke event
612 public void onKeypress(final TKeypressEvent keypress
) {
613 if (keypress
.equals(kbTab
)
614 || keypress
.equals(kbShiftTab
)
616 // Squash tab and back-tab. They don't make sense in the TTable
621 // If editing, pass to that cell and do nothing else.
622 if (getSelectedCell().isEditing
) {
623 super.onKeypress(keypress
);
627 boolean forceGridAlign
= false;
629 if (keypress
.equals(kbLeft
)) {
631 if (selectedColumn
> 0) {
634 activate(columns
.get(selectedColumn
).get(selectedRow
));
635 } else if (keypress
.equals(kbRight
)) {
637 if (selectedColumn
< columns
.size() - 1) {
640 activate(columns
.get(selectedColumn
).get(selectedRow
));
641 } else if (keypress
.equals(kbUp
)) {
643 if (selectedRow
> 0) {
646 activate(columns
.get(selectedColumn
).get(selectedRow
));
647 } else if (keypress
.equals(kbDown
)) {
649 if (selectedRow
< rows
.size() - 1) {
652 activate(columns
.get(selectedColumn
).get(selectedRow
));
653 } else if (keypress
.equals(kbHome
)) {
654 // Home - leftmost column
656 activate(columns
.get(selectedColumn
).get(selectedRow
));
657 forceGridAlign
= true;
658 } else if (keypress
.equals(kbEnd
)) {
659 // End - rightmost column
660 selectedColumn
= columns
.size() - 1;
661 activate(columns
.get(selectedColumn
).get(selectedRow
));
662 forceGridAlign
= true;
663 } else if (keypress
.equals(kbPgUp
)) {
664 // PgUp - Treat like multiple up
665 for (int i
= 0; i
< getHeight() - 2; i
++) {
666 if (selectedRow
> 0) {
670 activate(columns
.get(selectedColumn
).get(selectedRow
));
671 forceGridAlign
= true;
672 } else if (keypress
.equals(kbPgDn
)) {
673 // PgDn - Treat like multiple up
674 for (int i
= 0; i
< getHeight() - 2; i
++) {
675 if (selectedRow
< rows
.size() - 1) {
679 activate(columns
.get(selectedColumn
).get(selectedRow
));
680 forceGridAlign
= true;
681 } else if (keypress
.equals(kbCtrlHome
)) {
682 // Ctrl-Home - go to top-left
685 activate(columns
.get(selectedColumn
).get(selectedRow
));
686 activate(columns
.get(selectedColumn
).get(selectedRow
));
687 forceGridAlign
= true;
688 } else if (keypress
.equals(kbCtrlEnd
)) {
689 // Ctrl-End - go to bottom-right
690 selectedRow
= rows
.size() - 1;
691 selectedColumn
= columns
.size() - 1;
692 activate(columns
.get(selectedColumn
).get(selectedRow
));
693 activate(columns
.get(selectedColumn
).get(selectedRow
));
694 forceGridAlign
= true;
697 super.onKeypress(keypress
);
700 // We may have scrolled off screen. Reset positions as needed to
701 // make the newly selected cell visible.
702 alignGrid(forceGridAlign
);
706 * Handle widget resize events.
708 * @param event resize event
711 public void onResize(final TResizeEvent event
) {
712 super.onResize(event
);
718 * Handle posted menu events.
720 * @param menu menu event
723 public void onMenu(final TMenuEvent menu
) {
724 switch (menu
.getId()) {
725 case TMenu
.MID_TABLE_VIEW_ROW_LABELS
:
726 showRowLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
728 case TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
:
729 showColumnLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
731 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
:
732 highlightRow
= getApplication().getMenuItem(menu
.getId()).getChecked();
734 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
:
735 highlightColumn
= getApplication().getMenuItem(menu
.getId()).getChecked();
737 case TMenu
.MID_TABLE_BORDER_NONE
:
738 case TMenu
.MID_TABLE_BORDER_ALL
:
739 case TMenu
.MID_TABLE_BORDER_RIGHT
:
740 case TMenu
.MID_TABLE_BORDER_LEFT
:
741 case TMenu
.MID_TABLE_BORDER_TOP
:
742 case TMenu
.MID_TABLE_BORDER_BOTTOM
:
743 case TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
:
744 case TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
:
745 case TMenu
.MID_TABLE_DELETE_LEFT
:
746 case TMenu
.MID_TABLE_DELETE_UP
:
747 case TMenu
.MID_TABLE_DELETE_ROW
:
748 case TMenu
.MID_TABLE_DELETE_COLUMN
:
749 case TMenu
.MID_TABLE_INSERT_LEFT
:
750 case TMenu
.MID_TABLE_INSERT_RIGHT
:
751 case TMenu
.MID_TABLE_INSERT_ABOVE
:
752 case TMenu
.MID_TABLE_INSERT_BELOW
:
754 case TMenu
.MID_TABLE_COLUMN_NARROW
:
755 columns
.get(selectedColumn
).width
--;
756 for (Cell cell
: getSelectedColumn().cells
) {
757 cell
.setWidth(columns
.get(selectedColumn
).width
);
758 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
760 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
761 for (Cell cell
: columns
.get(i
).cells
) {
762 cell
.setX(cell
.getX() - 1);
767 case TMenu
.MID_TABLE_COLUMN_WIDEN
:
768 columns
.get(selectedColumn
).width
++;
769 for (Cell cell
: getSelectedColumn().cells
) {
770 cell
.setWidth(columns
.get(selectedColumn
).width
);
771 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
773 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
774 for (Cell cell
: columns
.get(i
).cells
) {
775 cell
.setX(cell
.getX() + 1);
780 case TMenu
.MID_TABLE_FILE_SAVE_CSV
:
783 case TMenu
.MID_TABLE_FILE_SAVE_TEXT
:
793 // ------------------------------------------------------------------------
794 // TWidget ----------------------------------------------------------------
795 // ------------------------------------------------------------------------
798 * Draw the table row/column labels, and borders.
802 CellAttributes labelColor
= getTheme().getColor("ttable.label");
803 CellAttributes labelColorSelected
= getTheme().getColor("ttable.label.selected");
804 CellAttributes borderColor
= getTheme().getColor("ttable.border");
807 if (showColumnLabels
== true) {
808 for (int i
= left
; i
< columns
.size(); i
++) {
809 if (columns
.get(i
).get(top
).isVisible() == false) {
812 putStringXY(columns
.get(i
).get(top
).getX(), 0,
813 String
.format(" %-" +
814 (columns
.get(i
).width
- 2)
815 + "s ", columns
.get(i
).label
),
816 (i
== selectedColumn ? labelColorSelected
: labelColor
));
821 if (showRowLabels
== true) {
822 for (int i
= top
; i
< rows
.size(); i
++) {
823 if (rows
.get(i
).get(left
).isVisible() == false) {
826 putStringXY(0, rows
.get(i
).get(left
).getY(),
827 String
.format(" %-6s ", rows
.get(i
).label
),
828 (i
== selectedRow ? labelColorSelected
: labelColor
));
832 // Now draw the window borders.
836 // ------------------------------------------------------------------------
837 // TTable -----------------------------------------------------------------
838 // ------------------------------------------------------------------------
841 * Get the currently-selected cell.
843 * @return the selected cell
845 public Cell
getSelectedCell() {
846 assert (rows
.get(selectedRow
) != null);
847 assert (rows
.get(selectedRow
).get(selectedColumn
) != null);
848 assert (columns
.get(selectedColumn
) != null);
849 assert (columns
.get(selectedColumn
).get(selectedRow
) != null);
850 assert (rows
.get(selectedRow
).get(selectedColumn
) ==
851 columns
.get(selectedColumn
).get(selectedRow
));
853 return (columns
.get(selectedColumn
).get(selectedRow
));
857 * Get the currently-selected column.
859 * @return the selected column
861 public Column
getSelectedColumn() {
862 assert (selectedColumn
>= 0);
863 assert (columns
.size() > selectedColumn
);
864 assert (columns
.get(selectedColumn
) != null);
865 return columns
.get(selectedColumn
);
869 * Get the currently-selected row.
871 * @return the selected row
873 public Row
getSelectedRow() {
874 assert (selectedRow
>= 0);
875 assert (rows
.size() > selectedRow
);
876 assert (rows
.get(selectedRow
) != null);
877 return rows
.get(selectedRow
);
881 * Get the currently-selected column number. 0 is the left-most column.
883 * @return the selected column number
885 public int getSelectedColumnNumber() {
886 return selectedColumn
;
890 * Set the currently-selected column number. 0 is the left-most column.
892 * @param column the column number to select
894 public void setSelectedColumnNumber(final int column
) {
895 if ((column
< 0) || (column
> columns
.size() - 1)) {
896 throw new IndexOutOfBoundsException("Column count is " +
897 columns
.size() + ", requested index " + column
);
899 selectedColumn
= column
;
900 activate(columns
.get(selectedColumn
).get(selectedRow
));
905 * Get the currently-selected row number. 0 is the top-most row.
907 * @return the selected row number
909 public int getSelectedRowNumber() {
914 * Set the currently-selected row number. 0 is the left-most column.
916 * @param row the row number to select
918 public void setSelectedRowNumber(final int row
) {
919 if ((row
< 0) || (row
> rows
.size() - 1)) {
920 throw new IndexOutOfBoundsException("Row count is " +
921 rows
.size() + ", requested index " + row
);
924 activate(columns
.get(selectedColumn
).get(selectedRow
));
929 * Get the number of columns.
931 * @return the number of columns
933 public int getColumnCount() {
934 return columns
.size();
938 * Get the number of rows.
940 * @return the number of rows
942 public int getRowCount() {
947 * Align the grid so that the selected cell is fully visible.
949 * @param force if true, always move the grid as needed
951 private void alignGrid(final boolean force
) {
952 boolean resetRowY
= false;
953 boolean resetColumnX
= false;
955 if (selectedColumn
< left
) {
956 left
= selectedColumn
;
960 if (selectedRow
< top
) {
969 } else if ((getSelectedCell().getX() + getSelectedCell().getWidth() + 1 >
971 || (columns
.get(left
).get(0).getX() <
972 (showRowLabels
== true ? ROW_LABEL_WIDTH
: 0))
976 } else if ((getSelectedCell().getY() + getSelectedCell().getHeight() >
978 || (rows
.get(top
).get(0).getY() <
979 (showColumnLabels
== true ? COLUMN_LABEL_HEIGHT
: 0))
985 if ((resetColumnX
== false) && (resetRowY
== false)) {
986 // Nothing to do, bail out.
991 * We start by assuming that all cells are visible, and then mark as
992 * invisible those that are outside the viewable area.
994 for (int x
= 0; x
< columns
.size(); x
++) {
995 for (int y
= 0; y
< rows
.size(); y
++) {
996 Cell cell
= rows
.get(y
).get(x
);
997 cell
.setVisible(true);
999 // Special case: mouse double-clicks can lead to
1000 // multiple cells in editing mode. Only allow a cell
1001 // to remain editing if it is fact the active widget.
1002 if (cell
.isEditing
&& !cell
.isActive()) {
1003 cell
.fieldText
= cell
.field
.getText();
1004 cell
.isEditing
= false;
1005 cell
.field
.setEnabled(false);
1010 // Adjust X locations to be visible -----------------------------------
1012 // Determine if we need to shift left or right.
1014 if (showRowLabels
== true) {
1015 // For now, all row labels are 8 cells wide. TODO: make this
1017 leftCellX
+= ROW_LABEL_WIDTH
;
1019 Row row
= getSelectedRow();
1020 Cell selectedColumnCell
= null;
1021 for (int i
= 0; i
< row
.cells
.size(); i
++) {
1022 if (i
== selectedColumn
) {
1023 selectedColumnCell
= row
.cells
.get(i
);
1026 leftCellX
+= row
.cells
.get(i
).getWidth() + 1;
1028 // There should always be a selected column.
1029 assert (selectedColumnCell
!= null);
1031 if (resetColumnX
== true) {
1033 // We need to adjust everything so that the selected cell is
1036 int excessWidth
= leftCellX
+ selectedColumnCell
.getWidth() + 1 - getWidth();
1037 if (excessWidth
> 0) {
1038 leftCellX
-= excessWidth
;
1040 if (leftCellX
< 0) {
1041 if (showRowLabels
== true) {
1042 leftCellX
= ROW_LABEL_WIDTH
;
1049 * leftCellX now contains the basic left offset necessary to draw
1050 * the cells such that the selected cell (column) is fully
1051 * visible within this widget's given width. Or, if the widget
1052 * is too narrow to display the full cell, leftCellX is 0 or
1055 * Now reset all of the X positions of the other cells so that
1056 * the selected cell X is leftCellX.
1058 for (int y
= 0; y
< rows
.size(); y
++) {
1059 // All cells to the left of selected cell.
1060 int newCellX
= leftCellX
;
1061 left
= selectedColumn
- 1;
1062 for (int x
= selectedColumn
- 1; x
>= 0; x
--) {
1063 newCellX
-= rows
.get(y
).get(x
).getWidth() + 1;
1064 rows
.get(y
).get(x
).setX(newCellX
);
1065 if (newCellX
>= (showRowLabels ? ROW_LABEL_WIDTH
: 0)) {
1066 if ((rows
.get(y
).get(0).getY() < (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0))
1067 || (rows
.get(y
).get(0).getY() >= getHeight())
1069 // This row isn't visible.
1070 rows
.get(y
).get(x
).setVisible(false);
1072 rows
.get(y
).get(x
).setVisible(true);
1076 // This cell won't be visible.
1077 rows
.get(y
).get(x
).setVisible(false);
1083 rows
.get(y
).get(selectedColumn
).setX(leftCellX
);
1084 if ((rows
.get(y
).get(0).getY() < (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0))
1085 || (rows
.get(y
).get(0).getY() >= getHeight())
1087 // This row isn't visible.
1088 rows
.get(y
).get(selectedColumn
).setVisible(false);
1090 rows
.get(y
).get(selectedColumn
).setVisible(true);
1093 assert (rows
.get(y
).get(left
).getX() >= 0);
1094 assert (rows
.get(y
).get(left
).getX() + rows
.get(y
).get(left
).getWidth() >= (showRowLabels ? ROW_LABEL_WIDTH
: 0));
1096 // All cells to the right of selected cell.
1097 newCellX
= leftCellX
+ selectedColumnCell
.getWidth() + 1;
1098 for (int x
= selectedColumn
+ 1; x
< columns
.size(); x
++) {
1099 rows
.get(y
).get(x
).setX(newCellX
);
1100 if (newCellX
<= getWidth()) {
1101 if ((rows
.get(y
).get(0).getY() < (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0))
1102 || (rows
.get(y
).get(0).getY() >= getHeight())
1104 // This row isn't visible.
1105 rows
.get(y
).get(x
).setVisible(false);
1107 rows
.get(y
).get(x
).setVisible(true);
1110 // This cell won't be visible.
1111 rows
.get(y
).get(x
).setVisible(false);
1113 newCellX
+= rows
.get(y
).get(x
).getWidth() + 1;
1117 } // if (resetColumnX == true)
1119 // Adjust Y locations to be visible -----------------------------------
1120 // The same logic as above, but applied to the column Y.
1122 // Determine if we need to shift up or down.
1124 if (showColumnLabels
== true) {
1125 // For now, all column labels are 1 cell high. TODO: make this
1129 Column column
= getSelectedColumn();
1130 Cell selectedRowCell
= null;
1131 for (int i
= 0; i
< column
.cells
.size(); i
++) {
1132 if (i
== selectedRow
) {
1133 selectedRowCell
= column
.cells
.get(i
);
1136 topCellY
+= column
.cells
.get(i
).getHeight();
1137 // TODO: if a border is selected, add 1 to topCellY.
1139 // There should always be a selected row.
1140 assert (selectedRowCell
!= null);
1142 if (resetRowY
== true) {
1144 // We need to adjust everything so that the selected cell is
1147 int excessHeight
= topCellY
+ selectedRowCell
.getHeight() - getHeight() - 1;
1148 if (showColumnLabels
== true) {
1149 excessHeight
+= COLUMN_LABEL_HEIGHT
;
1151 if (excessHeight
> 0) {
1152 topCellY
-= excessHeight
;
1155 if (showColumnLabels
== true) {
1156 topCellY
= COLUMN_LABEL_HEIGHT
;
1163 * topCellY now contains the basic top offset necessary to draw
1164 * the cells such that the selected cell (row) is fully visible
1165 * within this widget's given height. Or, if the widget is too
1166 * short to display the full cell, topCellY is 0 or
1167 * COLUMN_LABEL_HEIGHT.
1169 * Now reset all of the Y positions of the other cells so that
1170 * the selected cell Y is topCellY.
1172 for (int x
= 0; x
< columns
.size(); x
++) {
1173 // All cells above the selected cell.
1174 int newCellY
= topCellY
;
1175 top
= selectedRow
- 1;
1176 for (int y
= selectedRow
- 1; y
>= 0; y
--) {
1177 newCellY
-= columns
.get(x
).get(y
).getHeight();
1178 columns
.get(x
).get(y
).setY(newCellY
);
1179 if (newCellY
>= (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0)) {
1180 if ((columns
.get(x
).get(0).getX() < (showRowLabels ? ROW_LABEL_WIDTH
: 0))
1181 || (columns
.get(x
).get(0).getX() >= getWidth())
1183 // This column isn't visible.
1184 columns
.get(x
).get(y
).setVisible(false);
1186 columns
.get(x
).get(y
).setVisible(true);
1190 // This cell won't be visible.
1191 columns
.get(x
).get(y
).setVisible(false);
1197 columns
.get(x
).get(selectedRow
).setY(topCellY
);
1198 if ((columns
.get(x
).get(0).getX() < (showRowLabels ? ROW_LABEL_WIDTH
: 0))
1199 || (columns
.get(x
).get(0).getX() >= getWidth())
1201 // This column isn't visible.
1202 columns
.get(x
).get(selectedRow
).setVisible(false);
1204 columns
.get(x
).get(selectedRow
).setVisible(true);
1207 assert (columns
.get(x
).get(top
).getY() >= 0);
1208 assert (columns
.get(x
).get(top
).getY() + columns
.get(x
).get(top
).getHeight() >= (showColumnLabels ? COLUMN_LABEL_HEIGHT
: 0));
1210 // All cells below the selected cell.
1211 newCellY
= topCellY
+ selectedRowCell
.getHeight();
1212 for (int y
= selectedRow
+ 1; y
< rows
.size(); y
++) {
1213 columns
.get(x
).get(y
).setY(newCellY
);
1214 if (newCellY
<= getHeight()) {
1215 if ((columns
.get(x
).get(0).getX() < (showRowLabels ? ROW_LABEL_WIDTH
: 0))
1216 || (columns
.get(x
).get(0).getX() >= getWidth())
1218 // This column isn't visible.
1219 columns
.get(x
).get(y
).setVisible(false);
1221 columns
.get(x
).get(y
).setVisible(true);
1224 // This cell won't be visible.
1225 columns
.get(x
).get(y
).setVisible(false);
1227 newCellY
+= columns
.get(x
).get(y
).getHeight();
1231 } // if (resetRowY == true)
1236 * Save contents to file.
1238 * @param filename file to save to
1239 * @throws IOException if a java.io operation throws
1241 public void saveToFilename(final String filename
) throws IOException
{