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