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).
83 // ------------------------------------------------------------------------
84 // Variables --------------------------------------------------------------
85 // ------------------------------------------------------------------------
88 * The underlying data, organized as columns.
90 private ArrayList
<Column
> columns
= new ArrayList
<Column
>();
93 * The underlying data, organized as rows.
95 private ArrayList
<Row
> rows
= new ArrayList
<Row
>();
98 * The row in model corresponding to the top-left visible cell.
103 * The column in model corresponding to the top-left visible cell.
105 private int left
= 0;
108 * The row in model corresponding to the currently selected cell.
110 private int selectedRow
= 0;
113 * The column in model corresponding to the currently selected cell.
115 private int selectedColumn
= 0;
118 * If true, highlight the entire row of the currently-selected cell.
120 private boolean highlightRow
= true;
123 * If true, highlight the entire column of the currently-selected cell.
125 private boolean highlightColumn
= true;
128 * If true, show the row labels as the first column.
130 private boolean showRowLabels
= true;
133 * If true, show the column labels as the first row.
135 private boolean showColumnLabels
= true;
138 * Column represents a column of cells.
140 public class Column
{
145 private int width
= 8;
148 * The cells of this column.
150 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
155 private String label
= "";
158 * The border for this column.
160 private Border border
= Border
.NONE
;
163 * Constructor sets label to lettered column.
165 * @param col column number to use for this column. Column 0 will be
166 * "A", column 1 will be "B", column 26 will be "AA", and so on.
169 StringBuilder sb
= new StringBuilder();
171 sb
.append((char) ('A' + (col
% 26)));
177 label
= sb
.reverse().toString();
181 * Add an entry to this column.
183 * @param cell the cell to add
185 public void add(final Cell cell
) {
190 * Get an entry from this column.
192 * @param row the entry index to get
193 * @return the cell at row
195 public Cell
get(final int row
) {
196 return cells
.get(row
);
201 * Row represents a row of cells.
208 private int height
= 1;
211 * The cells of this row.
213 private ArrayList
<Cell
> cells
= new ArrayList
<Cell
>();
218 private String label
= "";
221 * The border for this row.
223 private Border border
= Border
.NONE
;
226 * Constructor sets label to numbered row.
228 * @param row row number to use for this row
231 label
= Integer
.toString(row
);
235 * Add an entry to this column.
237 * @param cell the cell to add
239 public void add(final Cell cell
) {
244 * Get an entry from this row.
246 * @param column the entry index to get
247 * @return the cell at column
249 public Cell
get(final int column
) {
250 return cells
.get(column
);
256 * Cell represents an editable cell in the table. Normally, navigation
257 * to a cell only highlights it; pressing Enter or F2 will switch to
260 public class Cell
extends TWidget
{
262 // --------------------------------------------------------------------
263 // Variables ----------------------------------------------------------
264 // --------------------------------------------------------------------
267 * The field containing the cell's data.
269 private TField field
;
272 * The column of this cell.
277 * The row of this cell.
282 * If true, the cell is being edited.
284 private boolean isEditing
= false;
287 * Text of field before editing.
289 private String fieldText
;
291 // --------------------------------------------------------------------
292 // Constructors -------------------------------------------------------
293 // --------------------------------------------------------------------
296 * Public constructor.
298 * @param parent parent widget
299 * @param x column relative to parent
300 * @param y row relative to parent
301 * @param width width of widget
302 * @param height height of widget
303 * @param column column index of this cell
304 * @param row row index of this cell
306 public Cell(final TTableWidget parent
, final int x
, final int y
,
307 final int width
, final int height
, final int column
,
310 super(parent
, x
, y
, width
, height
);
311 this.column
= column
;
314 field
= addField(0, 0, width
, false);
315 field
.setEnabled(false);
316 field
.setBackgroundChar(' ');
319 // --------------------------------------------------------------------
320 // Event handlers -----------------------------------------------------
321 // --------------------------------------------------------------------
324 * Handle mouse double-click events.
326 * @param mouse mouse double-click event
329 public void onMouseDoubleClick(final TMouseEvent mouse
) {
330 // Use TWidget's code to pass the event to the children.
331 super.onMouseDown(mouse
);
333 // Double-click means to start editing.
334 fieldText
= field
.getText();
336 field
.setEnabled(true);
340 // Let the table know that I was activated.
341 ((TTableWidget
) getParent()).selectedRow
= row
;
342 ((TTableWidget
) getParent()).selectedColumn
= column
;
343 ((TTableWidget
) getParent()).alignGrid();
348 * Handle mouse press events.
350 * @param mouse mouse button press event
353 public void onMouseDown(final TMouseEvent mouse
) {
354 // Use TWidget's code to pass the event to the children.
355 super.onMouseDown(mouse
);
358 // Let the table know that I was activated.
359 ((TTableWidget
) getParent()).selectedRow
= row
;
360 ((TTableWidget
) getParent()).selectedColumn
= column
;
361 ((TTableWidget
) getParent()).alignGrid();
366 * Handle mouse release events.
368 * @param mouse mouse button release event
371 public void onMouseUp(final TMouseEvent mouse
) {
372 // Use TWidget's code to pass the event to the children.
373 super.onMouseDown(mouse
);
376 // Let the table know that I was activated.
377 ((TTableWidget
) getParent()).selectedRow
= row
;
378 ((TTableWidget
) getParent()).selectedColumn
= column
;
379 ((TTableWidget
) getParent()).alignGrid();
386 * @param keypress keystroke event
389 public void onKeypress(final TKeypressEvent keypress
) {
390 // System.err.println("Cell onKeypress: " + keypress);
393 if (keypress
.equals(kbEsc
)) {
394 // ESC cancels the edit.
395 field
.setText(fieldText
);
397 field
.setEnabled(false);
400 if (keypress
.equals(kbEnter
)) {
401 // Enter ends editing.
402 fieldText
= field
.getText();
404 field
.setEnabled(false);
407 // Pass down to field.
408 super.onKeypress(keypress
);
411 if (keypress
.equals(kbEnter
) || keypress
.equals(kbF2
)) {
412 // Enter or F2 starts editing.
413 fieldText
= field
.getText();
415 field
.setEnabled(true);
421 // --------------------------------------------------------------------
422 // TWidget ------------------------------------------------------------
423 // --------------------------------------------------------------------
430 TTableWidget table
= (TTableWidget
) getParent();
432 if (isAbsoluteActive()) {
434 field
.setActiveColorKey("tfield.active");
435 field
.setInactiveColorKey("tfield.inactive");
437 field
.setActiveColorKey("ttable.selected");
438 field
.setInactiveColorKey("ttable.selected");
440 } else if (((table
.selectedColumn
== column
)
441 && ((table
.selectedRow
== row
)
442 || (table
.highlightColumn
== true)))
443 || ((table
.selectedRow
== row
)
444 && ((table
.selectedColumn
== column
)
445 || (table
.highlightRow
== true)))
447 field
.setActiveColorKey("ttable.active");
448 field
.setInactiveColorKey("ttable.active");
450 field
.setActiveColorKey("ttable.active");
451 field
.setInactiveColorKey("ttable.inactive");
454 assert (isVisible() == true);
459 // --------------------------------------------------------------------
460 // TTable.Cell --------------------------------------------------------
461 // --------------------------------------------------------------------
468 public final String
getText() {
469 return field
.getText();
475 * @param text the new field text
477 public void setText(final String text
) {
483 // ------------------------------------------------------------------------
484 // Constructors -----------------------------------------------------------
485 // ------------------------------------------------------------------------
488 * Public constructor.
490 * @param parent parent widget
491 * @param x column relative to parent
492 * @param y row relative to parent
493 * @param width width of widget
494 * @param height height of widget
496 public TTableWidget(final TWidget parent
, final int x
, final int y
,
497 final int width
, final int height
) {
499 super(parent
, x
, y
, width
, height
);
501 // Initialize the starting row and column.
502 rows
.add(new Row(0));
503 columns
.add(new Column(0));
505 // Place a grid of cells that fit in this space.
507 for (int i
= 0; i
< height
; i
+= rows
.get(0).height
) {
509 for (int j
= 0; j
< width
; j
+= columns
.get(0).width
) {
510 Cell cell
= new Cell(this, j
, i
, columns
.get(0).width
,
511 rows
.get(0).height
, column
, row
);
513 cell
.setText("" + row
+ " " + column
);
514 rows
.get(row
).add(cell
);
515 columns
.get(column
).add(cell
);
516 if ((i
== 0) && (j
+ columns
.get(0).width
< width
)) {
517 columns
.add(new Column(column
+ 1));
521 if (i
+ rows
.get(0).height
< height
) {
522 rows
.add(new Row(row
+ 1));
526 activate(columns
.get(selectedColumn
).get(selectedRow
));
530 // Set the menu to match the flags.
531 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_ROW_LABELS
).
532 setChecked(showRowLabels
);
533 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
).
534 setChecked(showColumnLabels
);
535 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
).
536 setChecked(highlightRow
);
537 getApplication().getMenuItem(TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
).
538 setChecked(highlightColumn
);
543 // ------------------------------------------------------------------------
544 // Event handlers ---------------------------------------------------------
545 // ------------------------------------------------------------------------
548 * Handle mouse press events.
550 * @param mouse mouse button press event
553 public void onMouseDown(final TMouseEvent mouse
) {
554 if (mouse
.isMouseWheelUp() || mouse
.isMouseWheelDown()) {
555 // Treat wheel up/down as 3 up/down
556 TKeypressEvent keyEvent
;
557 if (mouse
.isMouseWheelUp()) {
558 keyEvent
= new TKeypressEvent(kbUp
);
560 keyEvent
= new TKeypressEvent(kbDown
);
562 for (int i
= 0; i
< 3; i
++) {
563 onKeypress(keyEvent
);
568 // Use TWidget's code to pass the event to the children.
569 super.onMouseDown(mouse
);
575 * @param keypress keystroke event
578 public void onKeypress(final TKeypressEvent keypress
) {
579 if (keypress
.equals(kbTab
)
580 || keypress
.equals(kbShiftTab
)
582 // Squash tab and back-tab. They don't make sense in the TTable
587 // If editing, pass to that cell and do nothing else.
588 if (getSelectedCell().isEditing
) {
589 super.onKeypress(keypress
);
593 if (keypress
.equals(kbLeft
)) {
595 if (selectedColumn
> 0) {
598 activate(columns
.get(selectedColumn
).get(selectedRow
));
599 } else if (keypress
.equals(kbRight
)) {
601 if (selectedColumn
< columns
.size() - 1) {
604 activate(columns
.get(selectedColumn
).get(selectedRow
));
605 } else if (keypress
.equals(kbUp
)) {
607 if (selectedRow
> 0) {
610 activate(columns
.get(selectedColumn
).get(selectedRow
));
611 } else if (keypress
.equals(kbDown
)) {
613 if (selectedRow
< rows
.size() - 1) {
616 activate(columns
.get(selectedColumn
).get(selectedRow
));
617 } else if (keypress
.equals(kbHome
)) {
618 // Home - leftmost column
620 activate(columns
.get(selectedColumn
).get(selectedRow
));
621 } else if (keypress
.equals(kbEnd
)) {
622 // End - rightmost column
623 selectedColumn
= columns
.size() - 1;
624 activate(columns
.get(selectedColumn
).get(selectedRow
));
625 } else if (keypress
.equals(kbPgUp
)) {
626 // PgUp - Treat like multiple up
627 for (int i
= 0; i
< getHeight() - 2; i
++) {
628 if (selectedRow
> 0) {
632 activate(columns
.get(selectedColumn
).get(selectedRow
));
633 } else if (keypress
.equals(kbPgDn
)) {
634 // PgDn - Treat like multiple up
635 for (int i
= 0; i
< getHeight() - 2; i
++) {
636 if (selectedRow
< rows
.size() - 1) {
640 activate(columns
.get(selectedColumn
).get(selectedRow
));
641 } else if (keypress
.equals(kbCtrlHome
)) {
642 // Ctrl-Home - go to top-left
645 activate(columns
.get(selectedColumn
).get(selectedRow
));
646 activate(columns
.get(selectedColumn
).get(selectedRow
));
647 } else if (keypress
.equals(kbCtrlEnd
)) {
648 // Ctrl-End - go to bottom-right
649 selectedRow
= rows
.size() - 1;
650 selectedColumn
= columns
.size() - 1;
651 activate(columns
.get(selectedColumn
).get(selectedRow
));
652 activate(columns
.get(selectedColumn
).get(selectedRow
));
655 super.onKeypress(keypress
);
658 // We may have scrolled off screen. Reset positions as needed to
659 // make the newly selected cell visible.
664 * Handle widget resize events.
666 * @param event resize event
669 public void onResize(final TResizeEvent event
) {
670 super.onResize(event
);
676 * Handle posted menu events.
678 * @param menu menu event
681 public void onMenu(final TMenuEvent menu
) {
682 switch (menu
.getId()) {
683 case TMenu
.MID_TABLE_VIEW_ROW_LABELS
:
684 showRowLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
686 case TMenu
.MID_TABLE_VIEW_COLUMN_LABELS
:
687 showColumnLabels
= getApplication().getMenuItem(menu
.getId()).getChecked();
689 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_ROW
:
690 highlightRow
= getApplication().getMenuItem(menu
.getId()).getChecked();
692 case TMenu
.MID_TABLE_VIEW_HIGHLIGHT_COLUMN
:
693 highlightColumn
= getApplication().getMenuItem(menu
.getId()).getChecked();
695 case TMenu
.MID_TABLE_BORDER_NONE
:
696 case TMenu
.MID_TABLE_BORDER_ALL
:
697 case TMenu
.MID_TABLE_BORDER_RIGHT
:
698 case TMenu
.MID_TABLE_BORDER_LEFT
:
699 case TMenu
.MID_TABLE_BORDER_TOP
:
700 case TMenu
.MID_TABLE_BORDER_BOTTOM
:
701 case TMenu
.MID_TABLE_BORDER_DOUBLE_BOTTOM
:
702 case TMenu
.MID_TABLE_BORDER_THICK_BOTTOM
:
703 case TMenu
.MID_TABLE_DELETE_LEFT
:
704 case TMenu
.MID_TABLE_DELETE_UP
:
705 case TMenu
.MID_TABLE_DELETE_ROW
:
706 case TMenu
.MID_TABLE_DELETE_COLUMN
:
707 case TMenu
.MID_TABLE_INSERT_LEFT
:
708 case TMenu
.MID_TABLE_INSERT_RIGHT
:
709 case TMenu
.MID_TABLE_INSERT_ABOVE
:
710 case TMenu
.MID_TABLE_INSERT_BELOW
:
712 case TMenu
.MID_TABLE_COLUMN_NARROW
:
713 columns
.get(selectedColumn
).width
--;
714 for (Cell cell
: getSelectedColumn().cells
) {
715 cell
.setWidth(columns
.get(selectedColumn
).width
);
716 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
718 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
719 for (Cell cell
: columns
.get(i
).cells
) {
720 cell
.setX(cell
.getX() - 1);
725 case TMenu
.MID_TABLE_COLUMN_WIDEN
:
726 columns
.get(selectedColumn
).width
++;
727 for (Cell cell
: getSelectedColumn().cells
) {
728 cell
.setWidth(columns
.get(selectedColumn
).width
);
729 cell
.field
.setWidth(columns
.get(selectedColumn
).width
);
731 for (int i
= selectedColumn
+ 1; i
< columns
.size(); i
++) {
732 for (Cell cell
: columns
.get(i
).cells
) {
733 cell
.setX(cell
.getX() + 1);
738 case TMenu
.MID_TABLE_FILE_SAVE_CSV
:
741 case TMenu
.MID_TABLE_FILE_SAVE_TEXT
:
751 // ------------------------------------------------------------------------
752 // TWidget ----------------------------------------------------------------
753 // ------------------------------------------------------------------------
756 * Draw the table row/column labels, and borders.
760 CellAttributes labelColor
= getTheme().getColor("ttable.label");
761 CellAttributes labelColorSelected
= getTheme().getColor("ttable.label.selected");
762 CellAttributes borderColor
= getTheme().getColor("ttable.border");
765 if (showColumnLabels
== true) {
766 for (int i
= left
; i
< columns
.size(); i
++) {
767 if (columns
.get(i
).get(top
).isVisible() == false) {
770 putStringXY(columns
.get(i
).get(top
).getX(), 0,
771 String
.format(" %-" +
772 (columns
.get(i
).get(top
).getWidth() - 2)
773 + "s ", columns
.get(i
).label
),
774 (i
== selectedColumn ? labelColorSelected
: labelColor
));
779 if (showRowLabels
== true) {
780 for (int i
= top
; i
< rows
.size(); i
++) {
781 if (rows
.get(i
).get(left
).isVisible() == false) {
784 putStringXY(0, rows
.get(i
).get(left
).getY(),
785 String
.format(" %-6s ", rows
.get(i
).label
),
786 (i
== selectedRow ? labelColorSelected
: labelColor
));
790 // Now draw the window borders.
794 // ------------------------------------------------------------------------
795 // TTable -----------------------------------------------------------------
796 // ------------------------------------------------------------------------
799 * Get the currently-selected cell.
801 * @return the selected cell
803 public Cell
getSelectedCell() {
804 assert (rows
.get(selectedRow
) != null);
805 assert (rows
.get(selectedRow
).get(selectedColumn
) != null);
806 assert (columns
.get(selectedColumn
) != null);
807 assert (columns
.get(selectedColumn
).get(selectedRow
) != null);
808 assert (rows
.get(selectedRow
).get(selectedColumn
) ==
809 columns
.get(selectedColumn
).get(selectedRow
));
811 return (columns
.get(selectedColumn
).get(selectedRow
));
815 * Get the currently-selected column.
817 * @return the selected column
819 public Column
getSelectedColumn() {
820 assert (selectedColumn
>= 0);
821 assert (columns
.size() > selectedColumn
);
822 assert (columns
.get(selectedColumn
) != null);
823 return columns
.get(selectedColumn
);
827 * Get the currently-selected row.
829 * @return the selected row
831 public Row
getSelectedRow() {
832 assert (selectedRow
>= 0);
833 assert (rows
.size() > selectedRow
);
834 assert (rows
.get(selectedRow
) != null);
835 return rows
.get(selectedRow
);
839 * Get the currently-selected column number. 0 is the left-most column.
841 * @return the selected column number
843 public int getSelectedColumnNumber() {
844 return selectedColumn
;
848 * Set the currently-selected column number. 0 is the left-most column.
850 * @param column the column number to select
852 public void setSelectedColumnNumber(final int column
) {
853 if ((column
< 0) || (column
> columns
.size() - 1)) {
854 throw new IndexOutOfBoundsException("Column count is " +
855 columns
.size() + ", requested index " + column
);
857 selectedColumn
= column
;
858 activate(columns
.get(selectedColumn
).get(selectedRow
));
863 * Get the currently-selected row number. 0 is the top-most row.
865 * @return the selected row number
867 public int getSelectedRowNumber() {
872 * Set the currently-selected row number. 0 is the left-most column.
874 * @param row the row number to select
876 public void setSelectedRowNumber(final int row
) {
877 if ((row
< 0) || (row
> rows
.size() - 1)) {
878 throw new IndexOutOfBoundsException("Row count is " +
879 rows
.size() + ", requested index " + row
);
882 activate(columns
.get(selectedColumn
).get(selectedRow
));
887 * Get the number of columns.
889 * @return the number of columns
891 public int getColumnCount() {
892 return columns
.size();
896 * Get the number of rows.
898 * @return the number of rows
900 public int getRowCount() {
905 * Get the full horizontal width of this table.
907 * @return the width required to render the entire table
909 private int getMaximumWidth() {
911 if (showRowLabels
== true) {
912 // For now, all row labels are 8 cells wide. TODO: make this
916 for (Cell cell
: getSelectedRow().cells
) {
917 totalWidth
+= cell
.getWidth() + 1;
923 * Get the full vertical height of this table.
925 * @return the height required to render the entire table
927 private int getMaximumHeight() {
929 if (showColumnLabels
== true) {
930 // For now, all column labels are 1 cell tall. TODO: make this
934 for (Cell cell
: getSelectedColumn().cells
) {
935 totalHeight
+= cell
.getHeight();
936 // TODO: handle top/bottom borders.
942 * Align the grid so that the selected cell is fully visible.
944 private void alignGrid() {
947 * We start by assuming that all cells are visible, and then mark as
948 * invisible those that are outside the viewable area.
950 for (int x
= 0; x
< columns
.size(); x
++) {
951 for (int y
= 0; y
< rows
.size(); y
++) {
952 Cell cell
= rows
.get(y
).cells
.get(x
);
953 cell
.setVisible(true);
955 // Special case: mouse double-clicks can lead to multiple
956 // cells in editing mode. Only allow a cell to remain
957 // editing if it is fact the active widget.
958 if (cell
.isEditing
&& !cell
.isActive()) {
959 cell
.fieldText
= cell
.field
.getText();
960 cell
.isEditing
= false;
961 cell
.field
.setEnabled(false);
966 // Adjust X locations to be visible -----------------------------------
968 // Determine if we need to shift left or right.
970 if (showRowLabels
== true) {
971 // For now, all row labels are 8 cells wide. TODO: make this
975 Row row
= getSelectedRow();
976 Cell selectedColumnCell
= null;
977 for (int i
= 0; i
< row
.cells
.size(); i
++) {
978 if (i
== selectedColumn
) {
979 selectedColumnCell
= row
.cells
.get(i
);
982 leftCellX
+= row
.cells
.get(i
).getWidth() + 1;
984 // There should always be a selected column.
985 assert (selectedColumnCell
!= null);
987 int excessWidth
= leftCellX
+ selectedColumnCell
.getWidth() + 1 - getWidth();
988 if (excessWidth
> 0) {
989 leftCellX
-= excessWidth
;
992 if (showRowLabels
== true) {
1000 * leftCellX now contains the basic left offset necessary to draw the
1001 * cells such that the selected cell (column) is fully visible within
1002 * this widget's given width. Or, if the widget is too narrow to
1003 * display the full cell, leftCellX is 0 or 8.
1005 * Now reset all of the X positions of the other cells so that the
1006 * selected cell X is leftCellX.
1008 for (int y
= 0; y
< rows
.size(); y
++) {
1009 // All cells to the left of selected cell.
1010 int newCellX
= leftCellX
;
1011 left
= selectedColumn
;
1012 for (int x
= selectedColumn
- 1; x
>= 0; x
--) {
1013 newCellX
-= rows
.get(y
).cells
.get(x
).getWidth() + 1;
1014 if (newCellX
>= (showRowLabels ?
8 : 0)) {
1015 rows
.get(y
).cells
.get(x
).setVisible(true);
1016 rows
.get(y
).cells
.get(x
).setX(newCellX
);
1019 // This cell won't be visible.
1020 rows
.get(y
).cells
.get(x
).setVisible(false);
1025 rows
.get(y
).cells
.get(selectedColumn
).setX(leftCellX
);
1026 assert (rows
.get(y
).cells
.get(selectedColumn
).isVisible());
1028 // All cells to the right of selected cell.
1029 newCellX
= leftCellX
+ selectedColumnCell
.getWidth() + 1;
1030 for (int x
= selectedColumn
+ 1; x
< columns
.size(); x
++) {
1031 if (newCellX
<= getWidth()) {
1032 rows
.get(y
).cells
.get(x
).setVisible(true);
1033 rows
.get(y
).cells
.get(x
).setX(newCellX
);
1035 // This cell won't be visible.
1036 rows
.get(y
).cells
.get(x
).setVisible(false);
1038 newCellX
+= rows
.get(y
).cells
.get(x
).getWidth() + 1;
1042 // Adjust Y locations to be visible -----------------------------------
1043 // The same logic as above, but applied to the column Y.
1045 // Determine if we need to shift up or down.
1047 if (showColumnLabels
== true) {
1048 // For now, all column labels are 1 cell high. TODO: make this
1052 Column column
= getSelectedColumn();
1053 Cell selectedRowCell
= null;
1054 for (int i
= 0; i
< column
.cells
.size(); i
++) {
1055 if (i
== selectedRow
) {
1056 selectedRowCell
= column
.cells
.get(i
);
1059 topCellY
+= column
.cells
.get(i
).getHeight();
1060 // TODO: if a border is selected, add 1 to topCellY.
1062 // There should always be a selected row.
1063 assert (selectedRowCell
!= null);
1065 int excessHeight
= topCellY
+ selectedRowCell
.getHeight() - getHeight() - 1;
1066 if (showColumnLabels
== true) {
1069 if (excessHeight
> 0) {
1070 topCellY
-= excessHeight
;
1073 if (showColumnLabels
== true) {
1081 * topCellY now contains the basic top offset necessary to draw the
1082 * cells such that the selected cell (row) is fully visible within
1083 * this widget's given height. Or, if the widget is too short to
1084 * display the full cell, topCellY is 0 or 1.
1086 * Now reset all of the Y positions of the other cells so that the
1087 * selected cell Y is topCellY.
1089 for (int x
= 0; x
< columns
.size(); x
++) {
1091 if (columns
.get(x
).get(0).isVisible() == false) {
1092 // This column won't be visible as determined by the checks
1093 // above, just continue to the next.
1097 // All cells above the selected cell.
1098 int newCellY
= topCellY
;
1100 for (int y
= selectedRow
- 1; y
>= 0; y
--) {
1101 newCellY
-= rows
.get(y
).cells
.get(x
).getHeight();
1102 if (newCellY
>= (showColumnLabels
== true ?
1 : 0)) {
1103 rows
.get(y
).cells
.get(x
).setVisible(true);
1104 rows
.get(y
).cells
.get(x
).setY(newCellY
);
1107 // This cell won't be visible.
1108 rows
.get(y
).cells
.get(x
).setVisible(false);
1113 columns
.get(x
).cells
.get(selectedRow
).setY(topCellY
);
1115 // All cells below the selected cell.
1116 newCellY
= topCellY
+ selectedRowCell
.getHeight();
1117 for (int y
= selectedRow
+ 1; y
< rows
.size(); y
++) {
1118 if (newCellY
<= getHeight()) {
1119 rows
.get(y
).cells
.get(x
).setVisible(true);
1120 rows
.get(y
).cells
.get(x
).setY(newCellY
);
1122 // This cell won't be visible.
1123 rows
.get(y
).cells
.get(x
).setVisible(false);
1125 newCellY
+= rows
.get(y
).cells
.get(x
).getHeight();
1132 * Save contents to file.
1134 * @param filename file to save to
1135 * @throws IOException if a java.io operation throws
1137 public void saveToFilename(final String filename
) throws IOException
{