ttable wip
[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;
2e1384cc 37import jexer.event.TResizeEvent;
1dac6b8d
KL
38import jexer.menu.TMenu;
39import 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 */
49public 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 */
9c172016 118 private boolean highlightRow = true;
1dac6b8d
KL
119
120 /**
121 * If true, highlight the entire column of the currently-selected cell.
122 */
9c172016
KL
123 private boolean highlightColumn = true;
124
125 /**
126 * If true, show the row labels as the first column.
127 */
128 private boolean showRowLabels = true;
1dac6b8d 129
68ee64d0
KL
130 /**
131 * If true, show the column labels as the first row.
132 */
133 private boolean showColumnLabels = true;
134
1dac6b8d
KL
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 */
68ee64d0 153 private String label = "";
1dac6b8d
KL
154
155 /**
156 * The border for this column.
157 */
158 private Border border = Border.NONE;
159
9c172016
KL
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
1dac6b8d
KL
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
9c172016
KL
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
1dac6b8d
KL
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
2e1384cc 312 field = addField(0, 0, width, false);
1dac6b8d 313 field.setEnabled(false);
1dac6b8d
KL
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
1dac6b8d 370 if (isAbsoluteActive()) {
9c172016
KL
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");
1dac6b8d 377 }
9c172016
KL
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");
1dac6b8d
KL
390 }
391
392 super.draw();
393 }
394
395 // --------------------------------------------------------------------
396 // TTable.Cell --------------------------------------------------------
397 // --------------------------------------------------------------------
398
399 /**
400 * Get field text.
401 *
402 * @return field text
403 */
404 public final String getText() {
405 return field.getText();
406 }
407
408 /**
409 * Set field text.
410 *
411 * @param text the new field text
412 */
413 public void setText(final String text) {
414 field.setText(text);
415 }
416
417 }
418
419 // ------------------------------------------------------------------------
420 // Constructors -----------------------------------------------------------
421 // ------------------------------------------------------------------------
422
423 /**
424 * Public constructor.
425 *
426 * @param parent parent widget
427 * @param x column relative to parent
428 * @param y row relative to parent
429 * @param width width of widget
430 * @param height height of widget
431 */
432 public TTableWidget(final TWidget parent, final int x, final int y,
433 final int width, final int height) {
434
435 super(parent, x, y, width, height);
436
437 // Initialize the starting row and column.
9c172016
KL
438 rows.add(new Row(0));
439 columns.add(new Column(0));
1dac6b8d
KL
440
441 // Place a grid of cells that fit in this space.
442 int row = 0;
443 for (int i = 0; i < height; i += rows.get(0).height) {
444 int column = 0;
445 for (int j = 0; j < width; j += columns.get(0).width) {
446 Cell cell = new Cell(this, j, i, columns.get(0).width,
447 rows.get(0).height, column, row);
448
449 cell.setText("" + row + " " + column);
450 rows.get(row).add(cell);
451 columns.get(column).add(cell);
452 if ((i == 0) && (j + columns.get(0).width < width)) {
9c172016 453 columns.add(new Column(column + 1));
1dac6b8d
KL
454 }
455 column++;
456 }
457 if (i + rows.get(0).height < height) {
9c172016 458 rows.add(new Row(row + 1));
1dac6b8d
KL
459 }
460 row++;
461 }
462 activate(columns.get(selectedColumn).get(selectedRow));
9c172016
KL
463
464 alignGrid();
1dac6b8d
KL
465 }
466
467 // ------------------------------------------------------------------------
468 // Event handlers ---------------------------------------------------------
469 // ------------------------------------------------------------------------
470
471 /**
472 * Handle keystrokes.
473 *
474 * @param keypress keystroke event
475 */
476 @Override
477 public void onKeypress(final TKeypressEvent keypress) {
478 if (keypress.equals(kbTab)
479 || keypress.equals(kbShiftTab)
480 ) {
481 // Squash tab and back-tab. They don't make sense in the TTable
482 // grid context.
483 return;
484 }
9c172016
KL
485
486 // If editing, pass to that cell and do nothing else.
1dac6b8d
KL
487 if (getSelectedCell().isEditing) {
488 super.onKeypress(keypress);
489 return;
490 }
491
492 if (keypress.equals(kbLeft)) {
493 if (selectedColumn > 0) {
494 selectedColumn--;
495 }
496 activate(columns.get(selectedColumn).get(selectedRow));
497 } else if (keypress.equals(kbRight)) {
498 if (selectedColumn < columns.size() - 1) {
499 selectedColumn++;
500 }
501 activate(columns.get(selectedColumn).get(selectedRow));
502 } else if (keypress.equals(kbUp)) {
503 if (selectedRow > 0) {
504 selectedRow--;
505 }
506 activate(columns.get(selectedColumn).get(selectedRow));
507 } else if (keypress.equals(kbDown)) {
508 if (selectedRow < rows.size() - 1) {
509 selectedRow++;
510 }
511 activate(columns.get(selectedColumn).get(selectedRow));
512 } else if (keypress.equals(kbHome)) {
513 selectedColumn = 0;
514 activate(columns.get(selectedColumn).get(selectedRow));
515 } else if (keypress.equals(kbEnd)) {
516 selectedColumn = columns.size() - 1;
517 activate(columns.get(selectedColumn).get(selectedRow));
518 } else if (keypress.equals(kbPgUp)) {
519 // TODO
520 } else if (keypress.equals(kbPgDn)) {
521 // TODO
522 } else if (keypress.equals(kbCtrlHome)) {
523 // TODO
524 } else if (keypress.equals(kbCtrlEnd)) {
525 // TODO
526 } else {
527 // Pass to the Cell.
528 super.onKeypress(keypress);
529 }
9c172016
KL
530
531 // We may have scrolled off screen. Reset positions as needed to
532 // make the newly selected cell visible.
533 alignGrid();
1dac6b8d
KL
534 }
535
2e1384cc
KL
536 /**
537 * Handle widget resize events.
538 *
539 * @param event resize event
540 */
541 @Override
542 public void onResize(final TResizeEvent event) {
543 super.onResize(event);
544
545 alignGrid();
546 }
547
1dac6b8d
KL
548 /**
549 * Handle posted menu events.
550 *
551 * @param menu menu event
552 */
553 @Override
554 public void onMenu(final TMenuEvent menu) {
555 switch (menu.getId()) {
556 case TMenu.MID_TABLE_BORDER_NONE:
557 case TMenu.MID_TABLE_BORDER_ALL:
558 case TMenu.MID_TABLE_BORDER_RIGHT:
559 case TMenu.MID_TABLE_BORDER_LEFT:
560 case TMenu.MID_TABLE_BORDER_TOP:
561 case TMenu.MID_TABLE_BORDER_BOTTOM:
562 case TMenu.MID_TABLE_BORDER_DOUBLE_BOTTOM:
563 case TMenu.MID_TABLE_BORDER_THICK_BOTTOM:
564 case TMenu.MID_TABLE_DELETE_LEFT:
565 case TMenu.MID_TABLE_DELETE_UP:
566 case TMenu.MID_TABLE_DELETE_ROW:
567 case TMenu.MID_TABLE_DELETE_COLUMN:
568 case TMenu.MID_TABLE_INSERT_LEFT:
569 case TMenu.MID_TABLE_INSERT_RIGHT:
570 case TMenu.MID_TABLE_INSERT_ABOVE:
571 case TMenu.MID_TABLE_INSERT_BELOW:
572 break;
573 case TMenu.MID_TABLE_COLUMN_NARROW:
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_COLUMN_WIDEN:
587 columns.get(selectedColumn).width++;
588 for (Cell cell: getSelectedColumn().cells) {
589 cell.setWidth(columns.get(selectedColumn).width);
590 cell.field.setWidth(columns.get(selectedColumn).width - 1);
591 }
9c172016
KL
592 for (int i = selectedColumn + 1; i < columns.size(); i++) {
593 for (Cell cell: columns.get(i).cells) {
594 cell.setX(cell.getX() + 1);
595 }
596 }
597 alignGrid();
1dac6b8d
KL
598 break;
599 case TMenu.MID_TABLE_FILE_SAVE_CSV:
600 case TMenu.MID_TABLE_FILE_SAVE_TEXT:
601 break;
602 default:
603 super.onMenu(menu);
604 }
605 }
606
607 // ------------------------------------------------------------------------
608 // TWidget ----------------------------------------------------------------
609 // ------------------------------------------------------------------------
610
68ee64d0
KL
611 /**
612 * Draw the table row/column headings, and borders.
613 */
614 @Override
615 public void draw() {
616 CellAttributes headingColor = getTheme().getColor("ttable.heading");
2e1384cc 617 CellAttributes headingColorSelected = getTheme().getColor("ttable.heading.selected");
68ee64d0
KL
618 CellAttributes borderColor = getTheme().getColor("ttable.border");
619
620 // Column labels.
621 if (showColumnLabels == true) {
68ee64d0 622 for (int i = left; i < columns.size(); i++) {
2e1384cc
KL
623 if (columns.get(i).get(top).isVisible() == false) {
624 break;
625 }
626 putStringXY(columns.get(i).get(top).getX(), 0,
627 String.format(" %-6s ", columns.get(i).label),
628 (i == selectedColumn ? headingColorSelected : headingColor));
68ee64d0
KL
629 }
630 }
631
632 // Row labels.
633 if (showRowLabels == true) {
68ee64d0 634 for (int i = top; i < rows.size(); i++) {
2e1384cc
KL
635 if (rows.get(i).get(left).isVisible() == false) {
636 break;
637 }
638 putStringXY(0, rows.get(i).get(left).getY(),
639 String.format(" %-6s ", rows.get(i).label),
640 (i == selectedRow ? headingColorSelected : headingColor));
68ee64d0
KL
641 }
642 }
643
644 // Now draw the window borders.
645 super.draw();
646 }
647
1dac6b8d
KL
648 // ------------------------------------------------------------------------
649 // TTable -----------------------------------------------------------------
650 // ------------------------------------------------------------------------
651
652 /**
653 * Get the currently-selected cell.
654 *
655 * @return the selected cell
656 */
657 public Cell getSelectedCell() {
658 assert (rows.get(selectedRow) != null);
659 assert (rows.get(selectedRow).get(selectedColumn) != null);
660 assert (columns.get(selectedColumn) != null);
661 assert (columns.get(selectedColumn).get(selectedRow) != null);
662 assert (rows.get(selectedRow).get(selectedColumn) ==
663 columns.get(selectedColumn).get(selectedRow));
664
665 return (columns.get(selectedColumn).get(selectedRow));
666 }
667
668 /**
669 * Get the currently-selected column.
670 *
671 * @return the selected column
672 */
673 public Column getSelectedColumn() {
674 assert (selectedColumn >= 0);
675 assert (columns.size() > selectedColumn);
676 assert (columns.get(selectedColumn) != null);
677 return columns.get(selectedColumn);
678 }
679
680 /**
681 * Get the currently-selected row.
682 *
683 * @return the selected row
684 */
685 public Row getSelectedRow() {
686 assert (selectedRow >= 0);
687 assert (rows.size() > selectedRow);
688 assert (rows.get(selectedRow) != null);
689 return rows.get(selectedRow);
690 }
691
9c172016
KL
692 /**
693 * Get the full horizontal width of this table.
694 *
695 * @return the width required to render the entire table
696 */
697 public int getMaximumWidth() {
698 int totalWidth = 0;
699 if (showRowLabels == true) {
700 // For now, all row labels are 8 cells wide. TODO: make this
701 // adjustable.
702 totalWidth += 8;
703 }
704 for (Cell cell: getSelectedRow().cells) {
705 totalWidth += cell.getWidth() + 1;
706 }
707 return totalWidth;
708 }
709
68ee64d0
KL
710 /**
711 * Get the full vertical height of this table.
712 *
713 * @return the height required to render the entire table
714 */
715 public int getMaximumHeight() {
716 int totalHeight = 0;
717 if (showColumnLabels == true) {
718 // For now, all column labels are 1 cell tall. TODO: make this
719 // adjustable.
720 totalHeight += 1;
721 }
722 for (Cell cell: getSelectedColumn().cells) {
723 totalHeight += cell.getHeight();
724 // TODO: handle top/bottom borders.
725 }
726 return totalHeight;
727 }
728
9c172016
KL
729 /**
730 * Align the grid so that the selected cell is fully visible.
731 */
732 private void alignGrid() {
68ee64d0 733
2e1384cc
KL
734 /*
735 * We start by assuming that all cells are visible, and then mark as
736 * invisible those that are outside the viewable area.
737 */
738 for (int x = 0; x < columns.size(); x++) {
739 for (int y = 0; y < rows.size(); y++) {
740 rows.get(y).cells.get(x).setVisible(true);
741 }
742 }
743
68ee64d0
KL
744 // Adjust X locations to be visible -----------------------------------
745
9c172016
KL
746 // Determine if we need to shift left or right.
747 int width = getMaximumWidth();
748 int leftCellX = 0;
749 if (showRowLabels == true) {
750 // For now, all row labels are 8 cells wide. TODO: make this
751 // adjustable.
752 leftCellX += 8;
753 }
68ee64d0
KL
754 Row row = getSelectedRow();
755 Cell selectedColumnCell = null;
756 for (int i = 0; i < row.cells.size(); i++) {
757 if (i == selectedColumn) {
758 selectedColumnCell = row.cells.get(i);
759 break;
760 }
761 leftCellX += row.cells.get(i).getWidth() + 1;
762 }
763 // There should always be a selected column.
764 assert (selectedColumnCell != null);
9c172016 765
2e1384cc
KL
766 int excessWidth = leftCellX + selectedColumnCell.getWidth() + 1 - getWidth();
767 if (excessWidth > 0) {
768 leftCellX -= excessWidth;
68ee64d0
KL
769 }
770 if (leftCellX < 0) {
2e1384cc
KL
771 if (showRowLabels == true) {
772 leftCellX = 8;
773 } else {
774 leftCellX = 0;
775 }
68ee64d0 776 }
9c172016 777
68ee64d0
KL
778 /*
779 * leftCellX now contains the basic left offset necessary to draw the
780 * cells such that the selected cell (column) is fully visible within
781 * this widget's given width. Or, if the widget is too narrow to
2e1384cc 782 * display the full cell, leftCellX is 0 or 8.
68ee64d0
KL
783 *
784 * Now reset all of the X positions of the other cells so that the
785 * selected cell X is leftCellX.
786 */
787 for (int y = 0; y < rows.size(); y++) {
788 // All cells to the left of selected cell.
789 int newCellX = leftCellX;
790 left = selectedColumn;
791 for (int x = selectedColumn - 1; x >= 0; x--) {
2e1384cc
KL
792 newCellX -= rows.get(y).cells.get(x).getWidth() + 1;
793 if (newCellX - rows.get(y).cells.get(x).getWidth() + 1 >= 0) {
68ee64d0
KL
794 rows.get(y).cells.get(x).setVisible(true);
795 rows.get(y).cells.get(x).setX(newCellX);
796 left--;
797 } else {
798 // This cell won't be visible.
799 rows.get(y).cells.get(x).setVisible(false);
800 }
801 }
802
803 // Selected cell.
804 rows.get(y).cells.get(selectedColumn).setX(leftCellX);
2e1384cc 805 assert (rows.get(y).cells.get(selectedColumn).isVisible());
68ee64d0
KL
806
807 // All cells to the right of selected cell.
808 newCellX = leftCellX + selectedColumnCell.getWidth() + 1;
809 for (int x = selectedColumn + 1; x < columns.size(); x++) {
2e1384cc 810 if (newCellX <= getWidth()) {
68ee64d0
KL
811 rows.get(y).cells.get(x).setVisible(true);
812 rows.get(y).cells.get(x).setX(newCellX);
813 } else {
814 // This cell won't be visible.
815 rows.get(y).cells.get(x).setVisible(false);
816 }
817 newCellX += rows.get(y).cells.get(x).getWidth() + 1;
818 }
819 }
820
821 // Adjust Y locations to be visible -----------------------------------
822 // The same logic as above, but applied to the column Y.
9c172016 823
68ee64d0
KL
824 // Determine if we need to shift up or down.
825 int height = getMaximumHeight();
826 int topCellY = 0;
827 if (showColumnLabels == true) {
828 // For now, all column labels are 1 cell high. TODO: make this
829 // adjustable.
830 topCellY += 1;
831 }
832 Column column = getSelectedColumn();
833 Cell selectedRowCell = null;
834 for (int i = 0; i < column.cells.size(); i++) {
835 if (i == selectedRow) {
836 selectedRowCell = column.cells.get(i);
837 break;
838 }
839 topCellY += column.cells.get(i).getHeight();
840 // TODO: if a border is selected, add 1 to topCellY.
841 }
842 // There should always be a selected row.
843 assert (selectedRowCell != null);
844
2e1384cc
KL
845 int excessHeight = topCellY + selectedRowCell.getHeight() - getHeight() - 1;
846 if (showColumnLabels == true) {
847 excessHeight += 1;
848 }
849 if (excessHeight > 0) {
850 topCellY -= excessHeight;
68ee64d0
KL
851 }
852 if (topCellY < 0) {
2e1384cc
KL
853 if (showColumnLabels == true) {
854 topCellY = 1;
855 } else {
856 topCellY = 0;
857 }
68ee64d0
KL
858 }
859
860 /*
861 * topCellY now contains the basic top offset necessary to draw the
862 * cells such that the selected cell (row) is fully visible within
863 * this widget's given height. Or, if the widget is too short to
2e1384cc 864 * display the full cell, topCellY is 0 or 1.
68ee64d0
KL
865 *
866 * Now reset all of the Y positions of the other cells so that the
867 * selected cell Y is topCellY.
868 */
869 for (int x = 0; x < columns.size(); x++) {
2e1384cc
KL
870 if (columns.get(x).cells.get(0).isVisible() == false) {
871 // This entire column will not be visible, as determined by
872 // the width checks above. Do no further processing.
873 break;
874 }
875
68ee64d0
KL
876 // All cells above the selected cell.
877 int newCellY = topCellY;
878 top = selectedRow;
879 for (int y = selectedRow - 1; y >= 0; y--) {
880 newCellY -= rows.get(y).cells.get(x).getHeight();
2e1384cc 881 if (newCellY >= (showColumnLabels == true ? 1 : 0)) {
68ee64d0
KL
882 rows.get(y).cells.get(x).setVisible(true);
883 rows.get(y).cells.get(x).setY(newCellY);
884 top--;
885 } else {
886 // This cell won't be visible.
887 rows.get(y).cells.get(x).setVisible(false);
888 }
889 }
890
891 // Selected cell.
2e1384cc
KL
892 columns.get(x).cells.get(selectedRow).setY(topCellY);
893 assert (columns.get(x).cells.get(selectedRow).isVisible());
68ee64d0 894
2e1384cc 895 // All cells below the selected cell.
68ee64d0
KL
896 newCellY = topCellY + selectedRowCell.getHeight();
897 for (int y = selectedRow + 1; y < rows.size(); y++) {
2e1384cc 898 if (newCellY <= getHeight()) {
68ee64d0
KL
899 rows.get(y).cells.get(x).setVisible(true);
900 rows.get(y).cells.get(x).setY(newCellY);
901 } else {
902 // This cell won't be visible.
903 rows.get(y).cells.get(x).setVisible(false);
904 }
905 newCellY += rows.get(y).cells.get(x).getHeight();
906 }
907 }
9c172016
KL
908
909 }
910
1dac6b8d 911}