ttable navigation minimally working
[fanfix.git] / src / jexer / TTableWidget.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import jexer.bits.CellAttributes;
35 import jexer.event.TKeypressEvent;
36 import jexer.event.TMenuEvent;
37 import jexer.event.TResizeEvent;
38 import jexer.menu.TMenu;
39 import static jexer.TKeypress.*;
40
41 /**
42 * TTableWidget is used to display and edit regular two-dimensional tables of
43 * cells.
44 *
45 * This class was inspired by a TTable implementation originally developed by
46 * David "Niki" ROULET [niki@nikiroo.be], made available under MIT at
47 * https://github.com/nikiroo/jexer/tree/ttable_pull.
48 */
49 public class TTableWidget extends TWidget {
50
51 // ------------------------------------------------------------------------
52 // Constants --------------------------------------------------------------
53 // ------------------------------------------------------------------------
54
55 /**
56 * Available borders for cells.
57 */
58 public enum Border {
59 /**
60 * No border.
61 */
62 NONE,
63
64 /**
65 * Single bar: \u2502 (vertical) and \u2500 (horizontal).
66 */
67 SINGLE,
68
69 /**
70 * Double bar: \u2551 (vertical) and \u2550 (horizontal).
71 */
72 DOUBLE,
73
74 /**
75 * Thick bar: \u258C (vertical, left half block) and \u2580
76 * (horizontal, upper block).
77 */
78 THICK,
79 }
80
81 // ------------------------------------------------------------------------
82 // Variables --------------------------------------------------------------
83 // ------------------------------------------------------------------------
84
85 /**
86 * The underlying data, organized as columns.
87 */
88 private ArrayList<Column> columns = new ArrayList<Column>();
89
90 /**
91 * The underlying data, organized as rows.
92 */
93 private ArrayList<Row> rows = new ArrayList<Row>();
94
95 /**
96 * The row in model corresponding to the top-left visible cell.
97 */
98 private int top = 0;
99
100 /**
101 * The column in model corresponding to the top-left visible cell.
102 */
103 private int left = 0;
104
105 /**
106 * The row in model corresponding to the currently selected cell.
107 */
108 private int selectedRow = 0;
109
110 /**
111 * The column in model corresponding to the currently selected cell.
112 */
113 private int selectedColumn = 0;
114
115 /**
116 * If true, highlight the entire row of the currently-selected cell.
117 */
118 private boolean highlightRow = true;
119
120 /**
121 * If true, highlight the entire column of the currently-selected cell.
122 */
123 private boolean highlightColumn = true;
124
125 /**
126 * If true, show the row labels as the first column.
127 */
128 private boolean showRowLabels = true;
129
130 /**
131 * If true, show the column labels as the first row.
132 */
133 private boolean showColumnLabels = true;
134
135 /**
136 * Column represents a column of cells.
137 */
138 public class Column {
139
140 /**
141 * Width of column.
142 */
143 private int width = 8;
144
145 /**
146 * The cells of this column.
147 */
148 private ArrayList<Cell> cells = new ArrayList<Cell>();
149
150 /**
151 * Column label.
152 */
153 private String label = "";
154
155 /**
156 * The border for this column.
157 */
158 private Border border = Border.NONE;
159
160 /**
161 * Constructor sets label to lettered column.
162 *
163 * @param col column number to use for this column. Column 0 will be
164 * "A", column 1 will be "B", column 26 will be "AA", and so on.
165 */
166 Column(int col) {
167 StringBuilder sb = new StringBuilder();
168 for (;;) {
169 sb.append((char) ('A' + (col % 26)));
170 if (col < 26) {
171 break;
172 }
173 col /= 26;
174 }
175 label = sb.reverse().toString();
176 }
177
178 /**
179 * Add an entry to this column.
180 *
181 * @param cell the cell to add
182 */
183 public void add(final Cell cell) {
184 cells.add(cell);
185 }
186
187 /**
188 * Get an entry from this column.
189 *
190 * @param row the entry index to get
191 * @return the cell at row
192 */
193 public Cell get(final int row) {
194 return cells.get(row);
195 }
196 }
197
198 /**
199 * Row represents a row of cells.
200 */
201 public class Row {
202
203 /**
204 * Height of row.
205 */
206 private int height = 1;
207
208 /**
209 * The cells of this row.
210 */
211 private ArrayList<Cell> cells = new ArrayList<Cell>();
212
213 /**
214 * Row label.
215 */
216 private String label = "";
217
218 /**
219 * The border for this row.
220 */
221 private Border border = Border.NONE;
222
223 /**
224 * Constructor sets label to numbered row.
225 *
226 * @param row row number to use for this row
227 */
228 Row(final int row) {
229 label = Integer.toString(row);
230 }
231
232 /**
233 * Add an entry to this column.
234 *
235 * @param cell the cell to add
236 */
237 public void add(final Cell cell) {
238 cells.add(cell);
239 }
240
241 /**
242 * Get an entry from this row.
243 *
244 * @param column the entry index to get
245 * @return the cell at column
246 */
247 public Cell get(final int column) {
248 return cells.get(column);
249 }
250
251 }
252
253 /**
254 * Cell represents an editable cell in the table. Normally, navigation
255 * to a cell only highlights it; pressing Enter or F2 will switch to
256 * editing mode.
257 */
258 public class Cell extends TWidget {
259
260 // --------------------------------------------------------------------
261 // Variables ----------------------------------------------------------
262 // --------------------------------------------------------------------
263
264 /**
265 * The field containing the cell's data.
266 */
267 private TField field;
268
269 /**
270 * The column of this cell.
271 */
272 private int column;
273
274 /**
275 * The row of this cell.
276 */
277 private int row;
278
279 /**
280 * If true, the cell is being edited.
281 */
282 private boolean isEditing = false;
283
284 /**
285 * Text of field before editing.
286 */
287 private String fieldText;
288
289 // --------------------------------------------------------------------
290 // Constructors -------------------------------------------------------
291 // --------------------------------------------------------------------
292
293 /**
294 * Public constructor.
295 *
296 * @param parent parent widget
297 * @param x column relative to parent
298 * @param y row relative to parent
299 * @param width width of widget
300 * @param height height of widget
301 * @param column column index of this cell
302 * @param row row index of this cell
303 */
304 public Cell(final TTableWidget parent, final int x, final int y,
305 final int width, final int height, final int column,
306 final int row) {
307
308 super(parent, x, y, width, height);
309 this.column = column;
310 this.row = row;
311
312 field = addField(0, 0, width, false);
313 field.setEnabled(false);
314 field.setBackgroundChar(' ');
315 }
316
317 // --------------------------------------------------------------------
318 // Event handlers -----------------------------------------------------
319 // --------------------------------------------------------------------
320
321 /**
322 * Handle keystrokes.
323 *
324 * @param keypress keystroke event
325 */
326 @Override
327 public void onKeypress(final TKeypressEvent keypress) {
328 // System.err.println("Cell onKeypress: " + keypress);
329
330 if (isEditing) {
331 if (keypress.equals(kbEsc)) {
332 // ESC cancels the edit.
333 field.setText(fieldText);
334 isEditing = false;
335 field.setEnabled(false);
336 return;
337 }
338 if (keypress.equals(kbEnter)) {
339 // Enter ends editing.
340 fieldText = field.getText();
341 isEditing = false;
342 field.setEnabled(false);
343 return;
344 }
345 // Pass down to field.
346 super.onKeypress(keypress);
347 }
348
349 if (keypress.equals(kbEnter) || keypress.equals(kbF2)) {
350 // Enter or F2 starts editing.
351 fieldText = field.getText();
352 isEditing = true;
353 field.setEnabled(true);
354 activate(field);
355 return;
356 }
357 }
358
359 // --------------------------------------------------------------------
360 // TWidget ------------------------------------------------------------
361 // --------------------------------------------------------------------
362
363 /**
364 * Draw this cell.
365 */
366 @Override
367 public void draw() {
368 TTableWidget table = (TTableWidget) getParent();
369
370 if (isAbsoluteActive()) {
371 if (isEditing) {
372 field.setActiveColorKey("tfield.active");
373 field.setInactiveColorKey("tfield.inactive");
374 } else {
375 field.setActiveColorKey("ttable.selected");
376 field.setInactiveColorKey("ttable.selected");
377 }
378 } else if (((table.selectedColumn == column)
379 && ((table.selectedRow == row)
380 || (table.highlightColumn == true)))
381 || ((table.selectedRow == row)
382 && ((table.selectedColumn == column)
383 || (table.highlightRow == true)))
384 ) {
385 field.setActiveColorKey("ttable.active");
386 field.setInactiveColorKey("ttable.active");
387 } else {
388 field.setActiveColorKey("ttable.active");
389 field.setInactiveColorKey("ttable.inactive");
390 }
391
392 assert (isVisible() == true);
393
394 super.draw();
395 }
396
397 // --------------------------------------------------------------------
398 // TTable.Cell --------------------------------------------------------
399 // --------------------------------------------------------------------
400
401 /**
402 * Get field text.
403 *
404 * @return field text
405 */
406 public final String getText() {
407 return field.getText();
408 }
409
410 /**
411 * Set field text.
412 *
413 * @param text the new field text
414 */
415 public void setText(final String text) {
416 field.setText(text);
417 }
418
419 }
420
421 // ------------------------------------------------------------------------
422 // Constructors -----------------------------------------------------------
423 // ------------------------------------------------------------------------
424
425 /**
426 * Public constructor.
427 *
428 * @param parent parent widget
429 * @param x column relative to parent
430 * @param y row relative to parent
431 * @param width width of widget
432 * @param height height of widget
433 */
434 public TTableWidget(final TWidget parent, final int x, final int y,
435 final int width, final int height) {
436
437 super(parent, x, y, width, height);
438
439 // Initialize the starting row and column.
440 rows.add(new Row(0));
441 columns.add(new Column(0));
442
443 // Place a grid of cells that fit in this space.
444 int row = 0;
445 for (int i = 0; i < height; i += rows.get(0).height) {
446 int column = 0;
447 for (int j = 0; j < width; j += columns.get(0).width) {
448 Cell cell = new Cell(this, j, i, columns.get(0).width,
449 rows.get(0).height, column, row);
450
451 cell.setText("" + row + " " + column);
452 rows.get(row).add(cell);
453 columns.get(column).add(cell);
454 if ((i == 0) && (j + columns.get(0).width < width)) {
455 columns.add(new Column(column + 1));
456 }
457 column++;
458 }
459 if (i + rows.get(0).height < height) {
460 rows.add(new Row(row + 1));
461 }
462 row++;
463 }
464 activate(columns.get(selectedColumn).get(selectedRow));
465
466 alignGrid();
467
468 // Set the menu to match the flags.
469 getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_ROW_LABELS).
470 setChecked(showRowLabels);
471 getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_COLUMN_LABELS).
472 setChecked(showColumnLabels);
473 getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_HIGHLIGHT_ROW).
474 setChecked(highlightRow);
475 getApplication().getMenuItem(TMenu.MID_TABLE_VIEW_HIGHLIGHT_COLUMN).
476 setChecked(highlightColumn);
477
478
479 }
480
481 // ------------------------------------------------------------------------
482 // Event handlers ---------------------------------------------------------
483 // ------------------------------------------------------------------------
484
485 /**
486 * Handle keystrokes.
487 *
488 * @param keypress keystroke event
489 */
490 @Override
491 public void onKeypress(final TKeypressEvent keypress) {
492 if (keypress.equals(kbTab)
493 || keypress.equals(kbShiftTab)
494 ) {
495 // Squash tab and back-tab. They don't make sense in the TTable
496 // grid context.
497 return;
498 }
499
500 // If editing, pass to that cell and do nothing else.
501 if (getSelectedCell().isEditing) {
502 super.onKeypress(keypress);
503 return;
504 }
505
506 if (keypress.equals(kbLeft)) {
507 if (selectedColumn > 0) {
508 selectedColumn--;
509 }
510 activate(columns.get(selectedColumn).get(selectedRow));
511 } else if (keypress.equals(kbRight)) {
512 if (selectedColumn < columns.size() - 1) {
513 selectedColumn++;
514 }
515 activate(columns.get(selectedColumn).get(selectedRow));
516 } else if (keypress.equals(kbUp)) {
517 if (selectedRow > 0) {
518 selectedRow--;
519 }
520 activate(columns.get(selectedColumn).get(selectedRow));
521 } else if (keypress.equals(kbDown)) {
522 if (selectedRow < rows.size() - 1) {
523 selectedRow++;
524 }
525 activate(columns.get(selectedColumn).get(selectedRow));
526 } else if (keypress.equals(kbHome)) {
527 selectedColumn = 0;
528 activate(columns.get(selectedColumn).get(selectedRow));
529 } else if (keypress.equals(kbEnd)) {
530 selectedColumn = columns.size() - 1;
531 activate(columns.get(selectedColumn).get(selectedRow));
532 } else if (keypress.equals(kbPgUp)) {
533 // TODO
534 } else if (keypress.equals(kbPgDn)) {
535 // TODO
536 } else if (keypress.equals(kbCtrlHome)) {
537 // TODO
538 } else if (keypress.equals(kbCtrlEnd)) {
539 // TODO
540 } else {
541 // Pass to the Cell.
542 super.onKeypress(keypress);
543 }
544
545 // We may have scrolled off screen. Reset positions as needed to
546 // make the newly selected cell visible.
547 alignGrid();
548 }
549
550 /**
551 * Handle widget resize events.
552 *
553 * @param event resize event
554 */
555 @Override
556 public void onResize(final TResizeEvent event) {
557 super.onResize(event);
558
559 alignGrid();
560 }
561
562 /**
563 * Handle posted menu events.
564 *
565 * @param menu menu event
566 */
567 @Override
568 public void onMenu(final TMenuEvent menu) {
569 switch (menu.getId()) {
570 case TMenu.MID_TABLE_VIEW_ROW_LABELS:
571 showRowLabels = getApplication().getMenuItem(menu.getId()).getChecked();
572 break;
573 case TMenu.MID_TABLE_VIEW_COLUMN_LABELS:
574 showColumnLabels = getApplication().getMenuItem(menu.getId()).getChecked();
575 break;
576 case TMenu.MID_TABLE_VIEW_HIGHLIGHT_ROW:
577 highlightRow = getApplication().getMenuItem(menu.getId()).getChecked();
578 break;
579 case TMenu.MID_TABLE_VIEW_HIGHLIGHT_COLUMN:
580 highlightColumn = getApplication().getMenuItem(menu.getId()).getChecked();
581 break;
582 case TMenu.MID_TABLE_BORDER_NONE:
583 case TMenu.MID_TABLE_BORDER_ALL:
584 case TMenu.MID_TABLE_BORDER_RIGHT:
585 case TMenu.MID_TABLE_BORDER_LEFT:
586 case TMenu.MID_TABLE_BORDER_TOP:
587 case TMenu.MID_TABLE_BORDER_BOTTOM:
588 case TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM:
589 case TMenu.MID_TABLE_BORDER_THICK_BOTTOM:
590 case TMenu.MID_TABLE_DELETE_LEFT:
591 case TMenu.MID_TABLE_DELETE_UP:
592 case TMenu.MID_TABLE_DELETE_ROW:
593 case TMenu.MID_TABLE_DELETE_COLUMN:
594 case TMenu.MID_TABLE_INSERT_LEFT:
595 case TMenu.MID_TABLE_INSERT_RIGHT:
596 case TMenu.MID_TABLE_INSERT_ABOVE:
597 case TMenu.MID_TABLE_INSERT_BELOW:
598 break;
599 case TMenu.MID_TABLE_COLUMN_NARROW:
600 columns.get(selectedColumn).width--;
601 for (Cell cell: getSelectedColumn().cells) {
602 cell.setWidth(columns.get(selectedColumn).width);
603 cell.field.setWidth(columns.get(selectedColumn).width - 1);
604 }
605 for (int i = selectedColumn + 1; i < columns.size(); i++) {
606 for (Cell cell: columns.get(i).cells) {
607 cell.setX(cell.getX() - 1);
608 }
609 }
610 alignGrid();
611 break;
612 case TMenu.MID_TABLE_COLUMN_WIDEN:
613 columns.get(selectedColumn).width++;
614 for (Cell cell: getSelectedColumn().cells) {
615 cell.setWidth(columns.get(selectedColumn).width);
616 cell.field.setWidth(columns.get(selectedColumn).width - 1);
617 }
618 for (int i = selectedColumn + 1; i < columns.size(); i++) {
619 for (Cell cell: columns.get(i).cells) {
620 cell.setX(cell.getX() + 1);
621 }
622 }
623 alignGrid();
624 break;
625 case TMenu.MID_TABLE_FILE_SAVE_CSV:
626 case TMenu.MID_TABLE_FILE_SAVE_TEXT:
627 break;
628 default:
629 super.onMenu(menu);
630 }
631
632 alignGrid();
633 }
634
635 // ------------------------------------------------------------------------
636 // TWidget ----------------------------------------------------------------
637 // ------------------------------------------------------------------------
638
639 /**
640 * Draw the table row/column labels, and borders.
641 */
642 @Override
643 public void draw() {
644 CellAttributes labelColor = getTheme().getColor("ttable.label");
645 CellAttributes labelColorSelected = getTheme().getColor("ttable.label.selected");
646 CellAttributes borderColor = getTheme().getColor("ttable.border");
647
648 // Column labels.
649 if (showColumnLabels == true) {
650 for (int i = left; i < columns.size(); i++) {
651 if (columns.get(i).get(top).isVisible() == false) {
652 break;
653 }
654 putStringXY(columns.get(i).get(top).getX(), 0,
655 String.format(" %-6s ", columns.get(i).label),
656 (i == selectedColumn ? labelColorSelected : labelColor));
657 }
658 }
659
660 // Row labels.
661 if (showRowLabels == true) {
662 for (int i = top; i < rows.size(); i++) {
663 if (rows.get(i).get(left).isVisible() == false) {
664 break;
665 }
666 putStringXY(0, rows.get(i).get(left).getY(),
667 String.format(" %-6s ", rows.get(i).label),
668 (i == selectedRow ? labelColorSelected : labelColor));
669 }
670 }
671
672 // Now draw the window borders.
673 super.draw();
674 }
675
676 // ------------------------------------------------------------------------
677 // TTable -----------------------------------------------------------------
678 // ------------------------------------------------------------------------
679
680 /**
681 * Get the currently-selected cell.
682 *
683 * @return the selected cell
684 */
685 public Cell getSelectedCell() {
686 assert (rows.get(selectedRow) != null);
687 assert (rows.get(selectedRow).get(selectedColumn) != null);
688 assert (columns.get(selectedColumn) != null);
689 assert (columns.get(selectedColumn).get(selectedRow) != null);
690 assert (rows.get(selectedRow).get(selectedColumn) ==
691 columns.get(selectedColumn).get(selectedRow));
692
693 return (columns.get(selectedColumn).get(selectedRow));
694 }
695
696 /**
697 * Get the currently-selected column.
698 *
699 * @return the selected column
700 */
701 public Column getSelectedColumn() {
702 assert (selectedColumn >= 0);
703 assert (columns.size() > selectedColumn);
704 assert (columns.get(selectedColumn) != null);
705 return columns.get(selectedColumn);
706 }
707
708 /**
709 * Get the currently-selected row.
710 *
711 * @return the selected row
712 */
713 public Row getSelectedRow() {
714 assert (selectedRow >= 0);
715 assert (rows.size() > selectedRow);
716 assert (rows.get(selectedRow) != null);
717 return rows.get(selectedRow);
718 }
719
720 /**
721 * Get the full horizontal width of this table.
722 *
723 * @return the width required to render the entire table
724 */
725 public int getMaximumWidth() {
726 int totalWidth = 0;
727 if (showRowLabels == true) {
728 // For now, all row labels are 8 cells wide. TODO: make this
729 // adjustable.
730 totalWidth += 8;
731 }
732 for (Cell cell: getSelectedRow().cells) {
733 totalWidth += cell.getWidth() + 1;
734 }
735 return totalWidth;
736 }
737
738 /**
739 * Get the full vertical height of this table.
740 *
741 * @return the height required to render the entire table
742 */
743 public int getMaximumHeight() {
744 int totalHeight = 0;
745 if (showColumnLabels == true) {
746 // For now, all column labels are 1 cell tall. TODO: make this
747 // adjustable.
748 totalHeight += 1;
749 }
750 for (Cell cell: getSelectedColumn().cells) {
751 totalHeight += cell.getHeight();
752 // TODO: handle top/bottom borders.
753 }
754 return totalHeight;
755 }
756
757 /**
758 * Align the grid so that the selected cell is fully visible.
759 */
760 private void alignGrid() {
761
762 /*
763 * We start by assuming that all cells are visible, and then mark as
764 * invisible those that are outside the viewable area.
765 */
766 for (int x = 0; x < columns.size(); x++) {
767 for (int y = 0; y < rows.size(); y++) {
768 rows.get(y).cells.get(x).setVisible(true);
769 }
770 }
771
772 // Adjust X locations to be visible -----------------------------------
773
774 // Determine if we need to shift left or right.
775 int leftCellX = 0;
776 if (showRowLabels == true) {
777 // For now, all row labels are 8 cells wide. TODO: make this
778 // adjustable.
779 leftCellX += 8;
780 }
781 Row row = getSelectedRow();
782 Cell selectedColumnCell = null;
783 for (int i = 0; i < row.cells.size(); i++) {
784 if (i == selectedColumn) {
785 selectedColumnCell = row.cells.get(i);
786 break;
787 }
788 leftCellX += row.cells.get(i).getWidth() + 1;
789 }
790 // There should always be a selected column.
791 assert (selectedColumnCell != null);
792
793 int excessWidth = leftCellX + selectedColumnCell.getWidth() + 1 - getWidth();
794 if (excessWidth > 0) {
795 leftCellX -= excessWidth;
796 }
797 if (leftCellX < 0) {
798 if (showRowLabels == true) {
799 leftCellX = 8;
800 } else {
801 leftCellX = 0;
802 }
803 }
804
805 /*
806 * leftCellX now contains the basic left offset necessary to draw the
807 * cells such that the selected cell (column) is fully visible within
808 * this widget's given width. Or, if the widget is too narrow to
809 * display the full cell, leftCellX is 0 or 8.
810 *
811 * Now reset all of the X positions of the other cells so that the
812 * selected cell X is leftCellX.
813 */
814 for (int y = 0; y < rows.size(); y++) {
815 // All cells to the left of selected cell.
816 int newCellX = leftCellX;
817 left = selectedColumn;
818 for (int x = selectedColumn - 1; x >= 0; x--) {
819 newCellX -= rows.get(y).cells.get(x).getWidth() + 1;
820 if (newCellX - rows.get(y).cells.get(x).getWidth() + 1 >= 0) {
821 rows.get(y).cells.get(x).setVisible(true);
822 rows.get(y).cells.get(x).setX(newCellX);
823 left--;
824 } else {
825 // This cell won't be visible.
826 rows.get(y).cells.get(x).setVisible(false);
827 }
828 }
829
830 // Selected cell.
831 rows.get(y).cells.get(selectedColumn).setX(leftCellX);
832 assert (rows.get(y).cells.get(selectedColumn).isVisible());
833
834 // All cells to the right of selected cell.
835 newCellX = leftCellX + selectedColumnCell.getWidth() + 1;
836 for (int x = selectedColumn + 1; x < columns.size(); x++) {
837 if (newCellX <= getWidth()) {
838 rows.get(y).cells.get(x).setVisible(true);
839 rows.get(y).cells.get(x).setX(newCellX);
840 } else {
841 // This cell won't be visible.
842 rows.get(y).cells.get(x).setVisible(false);
843 }
844 newCellX += rows.get(y).cells.get(x).getWidth() + 1;
845 }
846 }
847
848 // Adjust Y locations to be visible -----------------------------------
849 // The same logic as above, but applied to the column Y.
850
851 // Determine if we need to shift up or down.
852 int topCellY = 0;
853 if (showColumnLabels == true) {
854 // For now, all column labels are 1 cell high. TODO: make this
855 // adjustable.
856 topCellY += 1;
857 }
858 Column column = getSelectedColumn();
859 Cell selectedRowCell = null;
860 for (int i = 0; i < column.cells.size(); i++) {
861 if (i == selectedRow) {
862 selectedRowCell = column.cells.get(i);
863 break;
864 }
865 topCellY += column.cells.get(i).getHeight();
866 // TODO: if a border is selected, add 1 to topCellY.
867 }
868 // There should always be a selected row.
869 assert (selectedRowCell != null);
870
871 int excessHeight = topCellY + selectedRowCell.getHeight() - getHeight() - 1;
872 if (showColumnLabels == true) {
873 excessHeight += 1;
874 }
875 if (excessHeight > 0) {
876 topCellY -= excessHeight;
877 }
878 if (topCellY < 0) {
879 if (showColumnLabels == true) {
880 topCellY = 1;
881 } else {
882 topCellY = 0;
883 }
884 }
885
886 /*
887 * topCellY now contains the basic top offset necessary to draw the
888 * cells such that the selected cell (row) is fully visible within
889 * this widget's given height. Or, if the widget is too short to
890 * display the full cell, topCellY is 0 or 1.
891 *
892 * Now reset all of the Y positions of the other cells so that the
893 * selected cell Y is topCellY.
894 */
895 for (int x = 0; x < columns.size(); x++) {
896
897 if (columns.get(x).get(0).isVisible() == false) {
898 // This column won't be visible as determined by the checks
899 // above, just continue to the next.
900 continue;
901 }
902
903 // All cells above the selected cell.
904 int newCellY = topCellY;
905 top = selectedRow;
906 for (int y = selectedRow - 1; y >= 0; y--) {
907 newCellY -= rows.get(y).cells.get(x).getHeight();
908 if (newCellY >= (showColumnLabels == true ? 1 : 0)) {
909 rows.get(y).cells.get(x).setVisible(true);
910 rows.get(y).cells.get(x).setY(newCellY);
911 top--;
912 } else {
913 // This cell won't be visible.
914 rows.get(y).cells.get(x).setVisible(false);
915 }
916 }
917
918 // Selected cell.
919 columns.get(x).cells.get(selectedRow).setY(topCellY);
920
921 // All cells below the selected cell.
922 newCellY = topCellY + selectedRowCell.getHeight();
923 for (int y = selectedRow + 1; y < rows.size(); y++) {
924 if (newCellY <= getHeight()) {
925 rows.get(y).cells.get(x).setVisible(true);
926 rows.get(y).cells.get(x).setY(newCellY);
927 } else {
928 // This cell won't be visible.
929 rows.get(y).cells.get(x).setVisible(false);
930 }
931 newCellY += rows.get(y).cells.get(x).getHeight();
932 }
933 }
934
935 }
936
937 }