fixup i18n
[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
759cb83e 31import java.io.IOException;
1dac6b8d
KL
32import java.util.ArrayList;
33import java.util.List;
34
68ee64d0 35import jexer.bits.CellAttributes;
1dac6b8d 36import jexer.event.TKeypressEvent;
759cb83e 37import jexer.event.TMouseEvent;
2e1384cc 38import jexer.event.TResizeEvent;
1dac6b8d
KL
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 /**
e9bb3c1e 75 * Thick bar: \u2503 (vertical heavy) and \u2501 (horizontal heavy).
1dac6b8d
KL
76 */
77 THICK,
78 }
79
f528c340
KL
80 /**
81 * If true, put a grid of numbers in the cells.
82 */
83 private static final boolean DEBUG = false;
84
73162e7f
KL
85 /**
86 * Row label width.
87 */
88 private static final int ROW_LABEL_WIDTH = 8;
89
90 /**
91 * Column label height.
92 */
93 private static final int COLUMN_LABEL_HEIGHT = 1;
94
11cedc9a
KL
95 /**
96 * Column default width.
97 */
98 private static final int COLUMN_DEFAULT_WIDTH = 8;
99
73162e7f
KL
100 /**
101 * Extra rows to add.
102 */
f528c340 103 private static final int EXTRA_ROWS = (DEBUG ? 10 : 0);
73162e7f
KL
104
105 /**
106 * Extra columns to add.
107 */
f528c340 108 private static final int EXTRA_COLUMNS = (DEBUG ? 10 * (8 + 1) : 0);
73162e7f 109
1dac6b8d
KL
110 // ------------------------------------------------------------------------
111 // Variables --------------------------------------------------------------
112 // ------------------------------------------------------------------------
113
114 /**
115 * The underlying data, organized as columns.
116 */
117 private ArrayList<Column> columns = new ArrayList<Column>();
118
119 /**
120 * The underlying data, organized as rows.
121 */
122 private ArrayList<Row> rows = new ArrayList<Row>();
123
124 /**
125 * The row in model corresponding to the top-left visible cell.
126 */
127 private int top = 0;
128
129 /**
130 * The column in model corresponding to the top-left visible cell.
131 */
132 private int left = 0;
133
134 /**
135 * The row in model corresponding to the currently selected cell.
136 */
137 private int selectedRow = 0;
138
139 /**
140 * The column in model corresponding to the currently selected cell.
141 */
142 private int selectedColumn = 0;
143
144 /**
145 * If true, highlight the entire row of the currently-selected cell.
146 */
73162e7f 147 private boolean highlightRow = false;
1dac6b8d
KL
148
149 /**
150 * If true, highlight the entire column of the currently-selected cell.
151 */
73162e7f 152 private boolean highlightColumn = false;
9c172016
KL
153
154 /**
155 * If true, show the row labels as the first column.
156 */
157 private boolean showRowLabels = true;
1dac6b8d 158
68ee64d0
KL
159 /**
160 * If true, show the column labels as the first row.
161 */
162 private boolean showColumnLabels = true;
163
73162e7f
KL
164 /**
165 * The top border for the first row.
166 */
167 private Border topBorder = Border.NONE;
168
169 /**
170 * The left border for the first column.
171 */
172 private Border leftBorder = Border.NONE;
173
1dac6b8d
KL
174 /**
175 * Column represents a column of cells.
176 */
177 public class Column {
178
11cedc9a
KL
179 /**
180 * X position of this column.
181 */
182 private int x = 0;
183
1dac6b8d
KL
184 /**
185 * Width of column.
186 */
11cedc9a 187 private int width = COLUMN_DEFAULT_WIDTH;
1dac6b8d
KL
188
189 /**
190 * The cells of this column.
191 */
192 private ArrayList<Cell> cells = new ArrayList<Cell>();
193
194 /**
195 * Column label.
196 */
68ee64d0 197 private String label = "";
1dac6b8d
KL
198
199 /**
73162e7f 200 * The right border for this column.
1dac6b8d 201 */
73162e7f 202 private Border rightBorder = Border.NONE;
1dac6b8d 203
9c172016
KL
204 /**
205 * Constructor sets label to lettered column.
206 *
207 * @param col column number to use for this column. Column 0 will be
208 * "A", column 1 will be "B", column 26 will be "AA", and so on.
209 */
210 Column(int col) {
2b427404 211 label = makeColumnLabel(col);
9c172016
KL
212 }
213
1dac6b8d
KL
214 /**
215 * Add an entry to this column.
216 *
217 * @param cell the cell to add
218 */
219 public void add(final Cell cell) {
220 cells.add(cell);
221 }
222
223 /**
224 * Get an entry from this column.
225 *
226 * @param row the entry index to get
227 * @return the cell at row
228 */
229 public Cell get(final int row) {
230 return cells.get(row);
231 }
11cedc9a
KL
232
233 /**
234 * Get the X position of the cells in this column.
235 *
236 * @return the position
237 */
238 public int getX() {
239 return x;
240 }
241
242 /**
243 * Set the X position of the cells in this column.
244 *
245 * @param x the position
246 */
247 public void setX(final int x) {
248 for (Cell cell: cells) {
249 cell.setX(x);
250 }
251 this.x = x;
252 }
253
1dac6b8d
KL
254 }
255
256 /**
257 * Row represents a row of cells.
258 */
259 public class Row {
260
11cedc9a
KL
261 /**
262 * Y position of this row.
263 */
264 private int y = 0;
265
1dac6b8d
KL
266 /**
267 * Height of row.
268 */
269 private int height = 1;
270
271 /**
272 * The cells of this row.
273 */
274 private ArrayList<Cell> cells = new ArrayList<Cell>();
275
276 /**
277 * Row label.
278 */
279 private String label = "";
280
281 /**
73162e7f 282 * The bottom border for this row.
1dac6b8d 283 */
73162e7f 284 private Border bottomBorder = Border.NONE;
1dac6b8d 285
9c172016
KL
286 /**
287 * Constructor sets label to numbered row.
288 *
289 * @param row row number to use for this row
290 */
291 Row(final int row) {
292 label = Integer.toString(row);
293 }
294
1dac6b8d
KL
295 /**
296 * Add an entry to this column.
297 *
298 * @param cell the cell to add
299 */
300 public void add(final Cell cell) {
301 cells.add(cell);
302 }
303
304 /**
305 * Get an entry from this row.
306 *
307 * @param column the entry index to get
308 * @return the cell at column
309 */
310 public Cell get(final int column) {
311 return cells.get(column);
312 }
11cedc9a
KL
313 /**
314 * Get the Y position of the cells in this column.
315 *
316 * @return the position
317 */
318 public int getY() {
319 return y;
320 }
321
322 /**
323 * Set the Y position of the cells in this column.
324 *
325 * @param y the position
326 */
327 public void setY(final int y) {
328 for (Cell cell: cells) {
329 cell.setY(y);
330 }
331 this.y = y;
332 }
1dac6b8d
KL
333
334 }
335
336 /**
337 * Cell represents an editable cell in the table. Normally, navigation
338 * to a cell only highlights it; pressing Enter or F2 will switch to
339 * editing mode.
340 */
341 public class Cell extends TWidget {
342
343 // --------------------------------------------------------------------
344 // Variables ----------------------------------------------------------
345 // --------------------------------------------------------------------
346
347 /**
348 * The field containing the cell's data.
349 */
350 private TField field;
351
352 /**
353 * The column of this cell.
354 */
355 private int column;
356
357 /**
358 * The row of this cell.
359 */
360 private int row;
361
362 /**
363 * If true, the cell is being edited.
364 */
365 private boolean isEditing = false;
366
2b427404
KL
367 /**
368 * If true, the cell is read-only (non-editable).
369 */
370 private boolean readOnly = false;
371
1dac6b8d
KL
372 /**
373 * Text of field before editing.
374 */
375 private String fieldText;
376
377 // --------------------------------------------------------------------
378 // Constructors -------------------------------------------------------
379 // --------------------------------------------------------------------
380
381 /**
382 * Public constructor.
383 *
384 * @param parent parent widget
385 * @param x column relative to parent
386 * @param y row relative to parent
387 * @param width width of widget
388 * @param height height of widget
389 * @param column column index of this cell
390 * @param row row index of this cell
391 */
392 public Cell(final TTableWidget parent, final int x, final int y,
393 final int width, final int height, final int column,
394 final int row) {
395
396 super(parent, x, y, width, height);
397 this.column = column;
398 this.row = row;
399
2e1384cc 400 field = addField(0, 0, width, false);
1dac6b8d 401 field.setEnabled(false);
1dac6b8d
KL
402 field.setBackgroundChar(' ');
403 }
404
405 // --------------------------------------------------------------------
406 // Event handlers -----------------------------------------------------
407 // --------------------------------------------------------------------
408
759cb83e
KL
409 /**
410 * Handle mouse double-click events.
411 *
412 * @param mouse mouse double-click event
413 */
414 @Override
415 public void onMouseDoubleClick(final TMouseEvent mouse) {
416 // Use TWidget's code to pass the event to the children.
417 super.onMouseDown(mouse);
418
419 // Double-click means to start editing.
420 fieldText = field.getText();
421 isEditing = true;
422 field.setEnabled(true);
423 activate(field);
424
425 if (isActive()) {
426 // Let the table know that I was activated.
427 ((TTableWidget) getParent()).selectedRow = row;
428 ((TTableWidget) getParent()).selectedColumn = column;
11cedc9a 429 ((TTableWidget) getParent()).alignGrid();
759cb83e
KL
430 }
431 }
432
433 /**
434 * Handle mouse press events.
435 *
436 * @param mouse mouse button press event
437 */
438 @Override
439 public void onMouseDown(final TMouseEvent mouse) {
440 // Use TWidget's code to pass the event to the children.
441 super.onMouseDown(mouse);
442
443 if (isActive()) {
444 // Let the table know that I was activated.
445 ((TTableWidget) getParent()).selectedRow = row;
446 ((TTableWidget) getParent()).selectedColumn = column;
11cedc9a 447 ((TTableWidget) getParent()).alignGrid();
759cb83e
KL
448 }
449 }
450
451 /**
452 * Handle mouse release events.
453 *
454 * @param mouse mouse button release event
455 */
456 @Override
457 public void onMouseUp(final TMouseEvent mouse) {
458 // Use TWidget's code to pass the event to the children.
459 super.onMouseDown(mouse);
460
461 if (isActive()) {
462 // Let the table know that I was activated.
463 ((TTableWidget) getParent()).selectedRow = row;
464 ((TTableWidget) getParent()).selectedColumn = column;
11cedc9a 465 ((TTableWidget) getParent()).alignGrid();
759cb83e
KL
466 }
467 }
468
1dac6b8d
KL
469 /**
470 * Handle keystrokes.
471 *
472 * @param keypress keystroke event
473 */
474 @Override
475 public void onKeypress(final TKeypressEvent keypress) {
476 // System.err.println("Cell onKeypress: " + keypress);
477
2b427404
KL
478 if (readOnly) {
479 // Read only: do nothing.
480 return;
481 }
482
1dac6b8d
KL
483 if (isEditing) {
484 if (keypress.equals(kbEsc)) {
485 // ESC cancels the edit.
2b427404 486 cancelEdit();
1dac6b8d
KL
487 return;
488 }
489 if (keypress.equals(kbEnter)) {
490 // Enter ends editing.
2b427404
KL
491
492 // Pass down to field first so that it can execute
493 // enterAction if specified.
494 super.onKeypress(keypress);
495
1dac6b8d
KL
496 fieldText = field.getText();
497 isEditing = false;
498 field.setEnabled(false);
499 return;
500 }
501 // Pass down to field.
502 super.onKeypress(keypress);
503 }
504
505 if (keypress.equals(kbEnter) || keypress.equals(kbF2)) {
506 // Enter or F2 starts editing.
507 fieldText = field.getText();
508 isEditing = true;
509 field.setEnabled(true);
510 activate(field);
511 return;
512 }
513 }
514
515 // --------------------------------------------------------------------
516 // TWidget ------------------------------------------------------------
517 // --------------------------------------------------------------------
518
519 /**
520 * Draw this cell.
521 */
522 @Override
523 public void draw() {
524 TTableWidget table = (TTableWidget) getParent();
525
1dac6b8d 526 if (isAbsoluteActive()) {
9c172016
KL
527 if (isEditing) {
528 field.setActiveColorKey("tfield.active");
529 field.setInactiveColorKey("tfield.inactive");
530 } else {
531 field.setActiveColorKey("ttable.selected");
532 field.setInactiveColorKey("ttable.selected");
1dac6b8d 533 }
9c172016
KL
534 } else if (((table.selectedColumn == column)
535 && ((table.selectedRow == row)
536 || (table.highlightColumn == true)))
537 || ((table.selectedRow == row)
538 && ((table.selectedColumn == column)
539 || (table.highlightRow == true)))
540 ) {
541 field.setActiveColorKey("ttable.active");
542 field.setInactiveColorKey("ttable.active");
543 } else {
544 field.setActiveColorKey("ttable.active");
545 field.setInactiveColorKey("ttable.inactive");
1dac6b8d
KL
546 }
547
77961919
KL
548 assert (isVisible() == true);
549
1dac6b8d
KL
550 super.draw();
551 }
552
553 // --------------------------------------------------------------------
554 // TTable.Cell --------------------------------------------------------
555 // --------------------------------------------------------------------
556
557 /**
558 * Get field text.
559 *
560 * @return field text
561 */
562 public final String getText() {
563 return field.getText();
564 }
565
566 /**
567 * Set field text.
568 *
569 * @param text the new field text
570 */
571 public void setText(final String text) {
572 field.setText(text);
573 }
574
2b427404
KL
575 /**
576 * Cancel any pending edit.
577 */
578 public void cancelEdit() {
579 // Cancel any pending edit.
580 if (fieldText != null) {
581 field.setText(fieldText);
582 }
583 isEditing = false;
584 field.setEnabled(false);
585 }
586
587 /**
588 * Set an entire column of cells read-only (non-editable) or not.
589 *
590 * @param readOnly if true, the cells will be non-editable
591 */
592 public void setReadOnly(final boolean readOnly) {
593 cancelEdit();
594 this.readOnly = readOnly;
595 }
596
1dac6b8d
KL
597 }
598
599 // ------------------------------------------------------------------------
600 // Constructors -----------------------------------------------------------
601 // ------------------------------------------------------------------------
602
603 /**
604 * Public constructor.
605 *
606 * @param parent parent widget
607 * @param x column relative to parent
608 * @param y row relative to parent
609 * @param width width of widget
610 * @param height height of widget
611 */
612 public TTableWidget(final TWidget parent, final int x, final int y,
613 final int width, final int height) {
614
615 super(parent, x, y, width, height);
616
617 // Initialize the starting row and column.
9c172016
KL
618 rows.add(new Row(0));
619 columns.add(new Column(0));
1dac6b8d
KL
620
621 // Place a grid of cells that fit in this space.
622 int row = 0;
73162e7f 623 for (int i = 0; i < height + EXTRA_ROWS; i += rows.get(0).height) {
1dac6b8d 624 int column = 0;
73162e7f
KL
625 for (int j = 0; j < width + EXTRA_COLUMNS;
626 j += columns.get(0).width) {
627
f528c340 628 Cell cell = new Cell(this, 0, 0, columns.get(0).width,
1dac6b8d
KL
629 rows.get(0).height, column, row);
630
f528c340
KL
631 if (DEBUG) {
632 // For debugging: set a grid of cell index labels.
633 cell.setText("" + row + " " + column);
634 }
1dac6b8d
KL
635 rows.get(row).add(cell);
636 columns.get(column).add(cell);
73162e7f
KL
637 if ((i == 0) &&
638 (j + columns.get(0).width < width + EXTRA_COLUMNS)
639 ) {
9c172016 640 columns.add(new Column(column + 1));
1dac6b8d
KL
641 }
642 column++;
643 }
73162e7f 644 if (i + rows.get(0).height < height + EXTRA_ROWS) {
9c172016 645 rows.add(new Row(row + 1));
1dac6b8d
KL
646 }
647 row++;
648 }
11cedc9a
KL
649 for (int i = 0; i < rows.size(); i++) {
650 rows.get(i).setY(i + (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0));
651 }
652 for (int j = 0; j < columns.size(); j++) {
653 columns.get(j).setX((j * COLUMN_DEFAULT_WIDTH) +
654 (showRowLabels ? ROW_LABEL_WIDTH : 0));
655 }
1dac6b8d 656 activate(columns.get(selectedColumn).get(selectedRow));
9c172016 657
11cedc9a 658 alignGrid();
1dac6b8d
KL
659 }
660
661 // ------------------------------------------------------------------------
662 // Event handlers ---------------------------------------------------------
663 // ------------------------------------------------------------------------
664
759cb83e
KL
665 /**
666 * Handle mouse press events.
667 *
668 * @param mouse mouse button press event
669 */
670 @Override
671 public void onMouseDown(final TMouseEvent mouse) {
672 if (mouse.isMouseWheelUp() || mouse.isMouseWheelDown()) {
673 // Treat wheel up/down as 3 up/down
674 TKeypressEvent keyEvent;
675 if (mouse.isMouseWheelUp()) {
676 keyEvent = new TKeypressEvent(kbUp);
677 } else {
678 keyEvent = new TKeypressEvent(kbDown);
679 }
680 for (int i = 0; i < 3; i++) {
681 onKeypress(keyEvent);
682 }
683 return;
684 }
685
686 // Use TWidget's code to pass the event to the children.
687 super.onMouseDown(mouse);
688 }
689
1dac6b8d
KL
690 /**
691 * Handle keystrokes.
692 *
693 * @param keypress keystroke event
694 */
695 @Override
696 public void onKeypress(final TKeypressEvent keypress) {
697 if (keypress.equals(kbTab)
698 || keypress.equals(kbShiftTab)
699 ) {
700 // Squash tab and back-tab. They don't make sense in the TTable
701 // grid context.
702 return;
703 }
9c172016
KL
704
705 // If editing, pass to that cell and do nothing else.
1dac6b8d
KL
706 if (getSelectedCell().isEditing) {
707 super.onKeypress(keypress);
708 return;
709 }
710
711 if (keypress.equals(kbLeft)) {
759cb83e 712 // Left
1dac6b8d
KL
713 if (selectedColumn > 0) {
714 selectedColumn--;
715 }
716 activate(columns.get(selectedColumn).get(selectedRow));
717 } else if (keypress.equals(kbRight)) {
759cb83e 718 // Right
1dac6b8d
KL
719 if (selectedColumn < columns.size() - 1) {
720 selectedColumn++;
721 }
722 activate(columns.get(selectedColumn).get(selectedRow));
723 } else if (keypress.equals(kbUp)) {
759cb83e 724 // Up
1dac6b8d
KL
725 if (selectedRow > 0) {
726 selectedRow--;
727 }
728 activate(columns.get(selectedColumn).get(selectedRow));
729 } else if (keypress.equals(kbDown)) {
759cb83e 730 // Down
1dac6b8d
KL
731 if (selectedRow < rows.size() - 1) {
732 selectedRow++;
733 }
734 activate(columns.get(selectedColumn).get(selectedRow));
735 } else if (keypress.equals(kbHome)) {
759cb83e 736 // Home - leftmost column
1dac6b8d
KL
737 selectedColumn = 0;
738 activate(columns.get(selectedColumn).get(selectedRow));
739 } else if (keypress.equals(kbEnd)) {
759cb83e 740 // End - rightmost column
1dac6b8d
KL
741 selectedColumn = columns.size() - 1;
742 activate(columns.get(selectedColumn).get(selectedRow));
743 } else if (keypress.equals(kbPgUp)) {
759cb83e
KL
744 // PgUp - Treat like multiple up
745 for (int i = 0; i < getHeight() - 2; i++) {
746 if (selectedRow > 0) {
747 selectedRow--;
748 }
749 }
750 activate(columns.get(selectedColumn).get(selectedRow));
1dac6b8d 751 } else if (keypress.equals(kbPgDn)) {
759cb83e
KL
752 // PgDn - Treat like multiple up
753 for (int i = 0; i < getHeight() - 2; i++) {
754 if (selectedRow < rows.size() - 1) {
755 selectedRow++;
756 }
757 }
758 activate(columns.get(selectedColumn).get(selectedRow));
1dac6b8d 759 } else if (keypress.equals(kbCtrlHome)) {
759cb83e
KL
760 // Ctrl-Home - go to top-left
761 selectedRow = 0;
762 selectedColumn = 0;
763 activate(columns.get(selectedColumn).get(selectedRow));
764 activate(columns.get(selectedColumn).get(selectedRow));
1dac6b8d 765 } else if (keypress.equals(kbCtrlEnd)) {
759cb83e
KL
766 // Ctrl-End - go to bottom-right
767 selectedRow = rows.size() - 1;
768 selectedColumn = columns.size() - 1;
769 activate(columns.get(selectedColumn).get(selectedRow));
770 activate(columns.get(selectedColumn).get(selectedRow));
1dac6b8d
KL
771 } else {
772 // Pass to the Cell.
773 super.onKeypress(keypress);
774 }
9c172016
KL
775
776 // We may have scrolled off screen. Reset positions as needed to
777 // make the newly selected cell visible.
11cedc9a 778 alignGrid();
1dac6b8d
KL
779 }
780
2e1384cc
KL
781 /**
782 * Handle widget resize events.
783 *
784 * @param event resize event
785 */
786 @Override
787 public void onResize(final TResizeEvent event) {
788 super.onResize(event);
789
ab8b0034 790 bottomRightCorner();
2e1384cc
KL
791 }
792
1dac6b8d
KL
793 // ------------------------------------------------------------------------
794 // TWidget ----------------------------------------------------------------
795 // ------------------------------------------------------------------------
796
68ee64d0 797 /**
77961919 798 * Draw the table row/column labels, and borders.
68ee64d0
KL
799 */
800 @Override
801 public void draw() {
77961919
KL
802 CellAttributes labelColor = getTheme().getColor("ttable.label");
803 CellAttributes labelColorSelected = getTheme().getColor("ttable.label.selected");
68ee64d0
KL
804 CellAttributes borderColor = getTheme().getColor("ttable.border");
805
806 // Column labels.
807 if (showColumnLabels == true) {
68ee64d0 808 for (int i = left; i < columns.size(); i++) {
2e1384cc
KL
809 if (columns.get(i).get(top).isVisible() == false) {
810 break;
811 }
e9bb3c1e 812 putStringXY(columns.get(i).get(top).getX(), 0,
759cb83e 813 String.format(" %-" +
73162e7f 814 (columns.get(i).width - 2)
759cb83e 815 + "s ", columns.get(i).label),
77961919 816 (i == selectedColumn ? labelColorSelected : labelColor));
68ee64d0
KL
817 }
818 }
819
820 // Row labels.
821 if (showRowLabels == true) {
68ee64d0 822 for (int i = top; i < rows.size(); i++) {
2e1384cc
KL
823 if (rows.get(i).get(left).isVisible() == false) {
824 break;
825 }
826 putStringXY(0, rows.get(i).get(left).getY(),
827 String.format(" %-6s ", rows.get(i).label),
77961919 828 (i == selectedRow ? labelColorSelected : labelColor));
68ee64d0
KL
829 }
830 }
831
91b3175a
KL
832 // Draw vertical borders.
833 if (leftBorder == Border.SINGLE) {
e9bb3c1e
KL
834 vLineXY((showRowLabels ? ROW_LABEL_WIDTH : 0),
835 (topBorder == Border.NONE ? 0 : 1) +
836 (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0),
91b3175a
KL
837 getHeight(), '\u2502', borderColor);
838 }
839 for (int i = left; i < columns.size(); i++) {
840 if (columns.get(i).get(top).isVisible() == false) {
841 break;
842 }
843 if (columns.get(i).rightBorder == Border.SINGLE) {
844 vLineXY(columns.get(i).getX() + columns.get(i).width,
e9bb3c1e
KL
845 (topBorder == Border.NONE ? 0 : 1) +
846 (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0),
91b3175a
KL
847 getHeight(), '\u2502', borderColor);
848 }
849 }
850
851 // Draw horizontal borders.
852 if (topBorder == Border.SINGLE) {
e9bb3c1e
KL
853 hLineXY((showRowLabels ? ROW_LABEL_WIDTH : 0),
854 (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0),
91b3175a
KL
855 getWidth(), '\u2500', borderColor);
856 }
857 for (int i = top; i < rows.size(); i++) {
858 if (rows.get(i).get(left).isVisible() == false) {
859 break;
860 }
861 if (rows.get(i).bottomBorder == Border.SINGLE) {
862 hLineXY((leftBorder == Border.NONE ? 0 : 1) +
863 (showRowLabels ? ROW_LABEL_WIDTH : 0),
864 rows.get(i).getY() + rows.get(i).height - 1,
865 getWidth(), '\u2500', borderColor);
866 } else if (rows.get(i).bottomBorder == Border.DOUBLE) {
867 hLineXY((leftBorder == Border.NONE ? 0 : 1) +
868 (showRowLabels ? ROW_LABEL_WIDTH : 0),
869 rows.get(i).getY() + rows.get(i).height - 1,
870 getWidth(), '\u2550', borderColor);
871 } else if (rows.get(i).bottomBorder == Border.THICK) {
872 hLineXY((leftBorder == Border.NONE ? 0 : 1) +
873 (showRowLabels ? ROW_LABEL_WIDTH : 0),
874 rows.get(i).getY() + rows.get(i).height - 1,
e9bb3c1e 875 getWidth(), '\u2501', borderColor);
91b3175a
KL
876 }
877 }
878 // Top-left corner if needed
879 if ((topBorder == Border.SINGLE) && (leftBorder == Border.SINGLE)) {
e9bb3c1e
KL
880 putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0),
881 (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0),
91b3175a
KL
882 '\u250c', borderColor);
883 }
884
e9bb3c1e
KL
885 // Now draw the correct corners
886 for (int i = top; i < rows.size(); i++) {
887 if (rows.get(i).get(left).isVisible() == false) {
888 break;
889 }
890 for (int j = left; j < columns.size(); j++) {
891 if (columns.get(j).get(i).isVisible() == false) {
892 break;
893 }
894 if ((i == top) && (topBorder == Border.SINGLE)
895 && (columns.get(j).rightBorder == Border.SINGLE)
896 ) {
897 // Top tee
898 putCharXY(columns.get(j).getX() + columns.get(j).width,
899 (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0),
900 '\u252c', borderColor);
901 }
902 if ((j == left) && (leftBorder == Border.SINGLE)
903 && (rows.get(i).bottomBorder == Border.SINGLE)
904 ) {
905 // Left tee
906 putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0),
907 rows.get(i).getY() + rows.get(i).height - 1,
908 '\u251c', borderColor);
909 }
910 if ((columns.get(j).rightBorder == Border.SINGLE)
911 && (rows.get(i).bottomBorder == Border.SINGLE)
912 ) {
913 // Intersection of single bars
914 putCharXY(columns.get(j).getX() + columns.get(j).width,
915 rows.get(i).getY() + rows.get(i).height - 1,
916 '\u253c', borderColor);
917 }
918 if ((j == left) && (leftBorder == Border.SINGLE)
919 && (rows.get(i).bottomBorder == Border.DOUBLE)
920 ) {
921 // Left tee: single bar vertical, double bar horizontal
922 putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0),
923 rows.get(i).getY() + rows.get(i).height - 1,
924 '\u255e', borderColor);
925 }
926 if ((j == left) && (leftBorder == Border.SINGLE)
927 && (rows.get(i).bottomBorder == Border.THICK)
928 ) {
929 // Left tee: single bar vertical, thick bar horizontal
930 putCharXY((showRowLabels ? ROW_LABEL_WIDTH : 0),
931 rows.get(i).getY() + rows.get(i).height - 1,
932 '\u251d', borderColor);
933 }
934 if ((columns.get(j).rightBorder == Border.SINGLE)
935 && (rows.get(i).bottomBorder == Border.DOUBLE)
936 ) {
937 // Intersection: single bar vertical, double bar
938 // horizontal
939 putCharXY(columns.get(j).getX() + columns.get(j).width,
940 rows.get(i).getY() + rows.get(i).height - 1,
941 '\u256a', borderColor);
942 }
943 if ((columns.get(j).rightBorder == Border.SINGLE)
944 && (rows.get(i).bottomBorder == Border.THICK)
945 ) {
946 // Intersection: single bar vertical, thick bar
947 // horizontal
948 putCharXY(columns.get(j).getX() + columns.get(j).width,
949 rows.get(i).getY() + rows.get(i).height - 1,
950 '\u253f', borderColor);
951 }
952 }
953 }
91b3175a 954
68ee64d0
KL
955 // Now draw the window borders.
956 super.draw();
957 }
958
1dac6b8d
KL
959 // ------------------------------------------------------------------------
960 // TTable -----------------------------------------------------------------
961 // ------------------------------------------------------------------------
962
2b427404
KL
963 /**
964 * Generate the default letter name for a column number.
965 *
966 * @param col column number to use for this column. Column 0 will be
967 * "A", column 1 will be "B", column 26 will be "AA", and so on.
968 */
969 private String makeColumnLabel(int col) {
970 StringBuilder sb = new StringBuilder();
971 for (;;) {
972 sb.append((char) ('A' + (col % 26)));
973 if (col < 26) {
974 break;
975 }
976 col /= 26;
977 }
978 return sb.reverse().toString();
979 }
980
1dac6b8d
KL
981 /**
982 * Get the currently-selected cell.
983 *
984 * @return the selected cell
985 */
986 public Cell getSelectedCell() {
987 assert (rows.get(selectedRow) != null);
988 assert (rows.get(selectedRow).get(selectedColumn) != null);
989 assert (columns.get(selectedColumn) != null);
990 assert (columns.get(selectedColumn).get(selectedRow) != null);
991 assert (rows.get(selectedRow).get(selectedColumn) ==
992 columns.get(selectedColumn).get(selectedRow));
993
994 return (columns.get(selectedColumn).get(selectedRow));
995 }
996
997 /**
998 * Get the currently-selected column.
999 *
1000 * @return the selected column
1001 */
1002 public Column getSelectedColumn() {
1003 assert (selectedColumn >= 0);
1004 assert (columns.size() > selectedColumn);
1005 assert (columns.get(selectedColumn) != null);
1006 return columns.get(selectedColumn);
1007 }
1008
1009 /**
1010 * Get the currently-selected row.
1011 *
1012 * @return the selected row
1013 */
1014 public Row getSelectedRow() {
1015 assert (selectedRow >= 0);
1016 assert (rows.size() > selectedRow);
1017 assert (rows.get(selectedRow) != null);
1018 return rows.get(selectedRow);
1019 }
1020
759cb83e
KL
1021 /**
1022 * Get the currently-selected column number. 0 is the left-most column.
1023 *
1024 * @return the selected column number
1025 */
1026 public int getSelectedColumnNumber() {
1027 return selectedColumn;
1028 }
1029
1030 /**
1031 * Set the currently-selected column number. 0 is the left-most column.
1032 *
1033 * @param column the column number to select
1034 */
1035 public void setSelectedColumnNumber(final int column) {
1036 if ((column < 0) || (column > columns.size() - 1)) {
1037 throw new IndexOutOfBoundsException("Column count is " +
1038 columns.size() + ", requested index " + column);
1039 }
1040 selectedColumn = column;
1041 activate(columns.get(selectedColumn).get(selectedRow));
11cedc9a 1042 alignGrid();
759cb83e
KL
1043 }
1044
1045 /**
1046 * Get the currently-selected row number. 0 is the top-most row.
1047 *
1048 * @return the selected row number
1049 */
1050 public int getSelectedRowNumber() {
1051 return selectedRow;
1052 }
1053
1054 /**
1055 * Set the currently-selected row number. 0 is the left-most column.
1056 *
1057 * @param row the row number to select
1058 */
1059 public void setSelectedRowNumber(final int row) {
1060 if ((row < 0) || (row > rows.size() - 1)) {
1061 throw new IndexOutOfBoundsException("Row count is " +
1062 rows.size() + ", requested index " + row);
1063 }
1064 selectedRow = row;
1065 activate(columns.get(selectedColumn).get(selectedRow));
11cedc9a 1066 alignGrid();
759cb83e
KL
1067 }
1068
f528c340
KL
1069 /**
1070 * Get the highlight row flag.
1071 *
1072 * @return true if the selected row is highlighted
1073 */
1074 public boolean getHighlightRow() {
1075 return highlightRow;
1076 }
1077
1078 /**
1079 * Set the highlight row flag.
1080 *
1081 * @param highlightRow if true, the selected row will be highlighted
1082 */
1083 public void setHighlightRow(final boolean highlightRow) {
1084 this.highlightRow = highlightRow;
1085 }
1086
1087 /**
1088 * Get the highlight column flag.
1089 *
1090 * @return true if the selected column is highlighted
1091 */
1092 public boolean getHighlightColumn() {
1093 return highlightColumn;
1094 }
1095
1096 /**
1097 * Set the highlight column flag.
1098 *
1099 * @param highlightColumn if true, the selected column will be highlighted
1100 */
1101 public void setHighlightColumn(final boolean highlightColumn) {
1102 this.highlightColumn = highlightColumn;
1103 }
1104
1105 /**
1106 * Get the show row labels flag.
1107 *
1108 * @return true if row labels are shown
1109 */
1110 public boolean getShowRowLabels() {
1111 return showRowLabels;
1112 }
1113
1114 /**
1115 * Set the show row labels flag.
1116 *
1117 * @param showRowLabels if true, the row labels will be shown
1118 */
1119 public void setShowRowLabels(final boolean showRowLabels) {
1120 this.showRowLabels = showRowLabels;
1121 }
1122
1123 /**
1124 * Get the show column labels flag.
1125 *
1126 * @return true if column labels are shown
1127 */
1128 public boolean getShowColumnLabels() {
1129 return showColumnLabels;
1130 }
1131
1132 /**
1133 * Set the show column labels flag.
1134 *
1135 * @param showColumnLabels if true, the column labels will be shown
1136 */
1137 public void setShowColumnLabels(final boolean showColumnLabels) {
1138 this.showColumnLabels = showColumnLabels;
1139 }
1140
759cb83e
KL
1141 /**
1142 * Get the number of columns.
1143 *
1144 * @return the number of columns
1145 */
1146 public int getColumnCount() {
1147 return columns.size();
1148 }
1149
1150 /**
1151 * Get the number of rows.
1152 *
1153 * @return the number of rows
1154 */
1155 public int getRowCount() {
1156 return rows.size();
1157 }
1158
ab8b0034
KL
1159
1160 /**
1161 * Push top and left to the bottom-most right corner of the available
1162 * grid.
1163 */
1164 private void bottomRightCorner() {
1165 int viewColumns = getWidth();
1166 if (showRowLabels == true) {
1167 viewColumns -= ROW_LABEL_WIDTH;
1168 }
1169
1170 // Set left and top such that the table stays on screen if possible.
1171 top = rows.size() - getHeight();
1172 left = columns.size() - (getWidth() / (viewColumns / (COLUMN_DEFAULT_WIDTH + 1)));
1173 // Now ensure the selection is visible.
1174 alignGrid();
1175 }
1176
9c172016
KL
1177 /**
1178 * Align the grid so that the selected cell is fully visible.
1179 */
11cedc9a
KL
1180 private void alignGrid() {
1181 int viewColumns = getWidth();
1182 if (showRowLabels == true) {
1183 viewColumns -= ROW_LABEL_WIDTH;
1184 }
1185 if (leftBorder != Border.NONE) {
1186 viewColumns--;
1187 }
1188 int viewRows = getHeight();
1189 if (showColumnLabels == true) {
1190 viewRows -= COLUMN_LABEL_HEIGHT;
1191 }
1192 if (topBorder != Border.NONE) {
1193 viewRows--;
1194 }
73162e7f 1195
11cedc9a
KL
1196 // If we pushed left or right, adjust the box to include the new
1197 // selected cell.
73162e7f 1198 if (selectedColumn < left) {
11cedc9a 1199 left = selectedColumn - 1;
73162e7f 1200 }
11cedc9a
KL
1201 if (left < 0) {
1202 left = 0;
73162e7f 1203 }
11cedc9a
KL
1204 if (selectedRow < top) {
1205 top = selectedRow - 1;
73162e7f 1206 }
11cedc9a
KL
1207 if (top < 0) {
1208 top = 0;
73162e7f 1209 }
68ee64d0 1210
2e1384cc 1211 /*
11cedc9a
KL
1212 * viewColumns and viewRows now contain the available columns and
1213 * rows available to view the selected cell. We adjust left and top
1214 * to ensure the selected cell is within view, and then make all
1215 * cells outside the box between (left, top) and (right, bottom)
1216 * invisible.
1217 *
1218 * We need to calculate right and bottom now.
2e1384cc 1219 */
11cedc9a
KL
1220 int right = left;
1221
1222 boolean done = false;
1223 while (!done) {
1224 int rightCellX = (showRowLabels ? ROW_LABEL_WIDTH : 0);
91b3175a
KL
1225 if (leftBorder != Border.NONE) {
1226 rightCellX++;
1227 }
11cedc9a
KL
1228 int maxCellX = rightCellX + viewColumns;
1229 right = left;
1230 boolean selectedIsVisible = false;
1231 int selectedX = 0;
1232 for (int x = left; x < columns.size(); x++) {
1233 if (x == selectedColumn) {
1234 selectedX = rightCellX;
1235 if (selectedX + columns.get(x).width + 1 <= maxCellX) {
1236 selectedIsVisible = true;
1237 }
759cb83e 1238 }
11cedc9a
KL
1239 rightCellX += columns.get(x).width + 1;
1240 if (rightCellX >= maxCellX) {
1241 break;
68ee64d0 1242 }
11cedc9a 1243 right++;
68ee64d0 1244 }
11cedc9a
KL
1245 if (right < selectedColumn) {
1246 // selectedColumn is outside the view range. Push left over,
1247 // and calculate again.
1248 left++;
1249 } else if (left == selectedColumn) {
1250 // selectedColumn doesn't fit inside the view range, but we
1251 // can't go over any further either. Bail out.
1252 done = true;
1253 } else if (selectedIsVisible == false) {
1254 // selectedColumn doesn't fit inside the view range, continue
1255 // on.
73162e7f 1256 left++;
11cedc9a
KL
1257 } else {
1258 // selectedColumn is fully visible, all done.
1259 assert (selectedIsVisible == true);
1260 done = true;
1261 }
73162e7f 1262
11cedc9a 1263 } // while (!done)
73162e7f 1264
11cedc9a
KL
1265 // We have the left/right range correct, set cell visibility and
1266 // column X positions.
1267 int leftCellX = showRowLabels ? ROW_LABEL_WIDTH : 0;
91b3175a
KL
1268 if (leftBorder != Border.NONE) {
1269 leftCellX++;
1270 }
11cedc9a
KL
1271 for (int x = 0; x < columns.size(); x++) {
1272 if ((x < left) || (x > right)) {
1273 for (int i = 0; i < rows.size(); i++) {
1274 columns.get(x).get(i).setVisible(false);
1275 columns.get(x).setX(getWidth() + 1);
68ee64d0 1276 }
11cedc9a 1277 continue;
68ee64d0 1278 }
11cedc9a
KL
1279 for (int i = 0; i < rows.size(); i++) {
1280 columns.get(x).get(i).setVisible(true);
68ee64d0 1281 }
11cedc9a
KL
1282 columns.get(x).setX(leftCellX);
1283 leftCellX += columns.get(x).width + 1;
68ee64d0 1284 }
68ee64d0 1285
11cedc9a 1286 int bottom = top;
77961919 1287
11cedc9a
KL
1288 done = false;
1289 while (!done) {
1290 int bottomCellY = (showColumnLabels ? COLUMN_LABEL_HEIGHT : 0);
91b3175a
KL
1291 if (topBorder != Border.NONE) {
1292 bottomCellY++;
1293 }
11cedc9a
KL
1294 int maxCellY = bottomCellY + viewRows;
1295 bottom = top;
1296 for (int y = top; y < rows.size(); y++) {
1297 bottomCellY += rows.get(y).height;
1298 if (bottomCellY >= maxCellY) {
1299 break;
68ee64d0 1300 }
11cedc9a 1301 bottom++;
68ee64d0 1302 }
11cedc9a
KL
1303 if (bottom < selectedRow) {
1304 // selectedRow is outside the view range. Push top down, and
1305 // calculate again.
73162e7f 1306 top++;
11cedc9a
KL
1307 } else {
1308 // selectedRow is inside the view range, done.
1309 done = true;
1310 }
1311 } // while (!done)
1312
1313 // We have the top/bottom range correct, set cell visibility and
1314 // row Y positions.
1315 int topCellY = showColumnLabels ? COLUMN_LABEL_HEIGHT : 0;
91b3175a
KL
1316 if (topBorder != Border.NONE) {
1317 topCellY++;
1318 }
11cedc9a
KL
1319 for (int y = 0; y < rows.size(); y++) {
1320 if ((y < top) || (y > bottom)) {
1321 for (int i = 0; i < columns.size(); i++) {
1322 rows.get(y).get(i).setVisible(false);
68ee64d0 1323 }
11cedc9a
KL
1324 rows.get(y).setY(getHeight() + 1);
1325 continue;
68ee64d0 1326 }
11cedc9a
KL
1327 for (int i = 0; i < columns.size(); i++) {
1328 rows.get(y).get(i).setVisible(true);
1329 }
1330 rows.get(y).setY(topCellY);
1331 topCellY += rows.get(y).height;
1332 }
9c172016 1333
2b427404
KL
1334 // Last thing: cancel any edits that are not the selected cell.
1335 for (int y = 0; y < rows.size(); y++) {
1336 for (int x = 0; x < columns.size(); x++) {
1337 if ((x == selectedColumn) && (y == selectedRow)) {
1338 continue;
1339 }
1340 rows.get(y).get(x).cancelEdit();
1341 }
1342 }
9c172016
KL
1343 }
1344
759cb83e 1345 /**
f528c340 1346 * Save contents to file in CSV format.
759cb83e
KL
1347 *
1348 * @param filename file to save to
1349 * @throws IOException if a java.io operation throws
1350 */
f528c340
KL
1351 public void saveToCsvFilename(final String filename) throws IOException {
1352 // TODO
1353 }
1354
1355 /**
1356 * Save contents to file in text format with lines.
1357 *
1358 * @param filename file to save to
1359 * @throws IOException if a java.io operation throws
1360 */
1361 public void saveToTextFilename(final String filename) throws IOException {
759cb83e
KL
1362 // TODO
1363 }
1364
2b427404
KL
1365 /**
1366 * Set the selected cell location.
1367 *
1368 * @param column the selected cell location column
1369 * @param row the selected cell location row
1370 */
1371 public void setSelectedCell(final int column, final int row) {
1372 if ((column < 0) || (column > columns.size() - 1)) {
1373 throw new IndexOutOfBoundsException("Column count is " +
1374 columns.size() + ", requested index " + column);
1375 }
1376 if ((row < 0) || (row > rows.size() - 1)) {
1377 throw new IndexOutOfBoundsException("Row count is " +
1378 rows.size() + ", requested index " + row);
1379 }
1380 selectedColumn = column;
1381 selectedRow = row;
1382 alignGrid();
1383 }
1384
1385 /**
1386 * Get a particular cell.
1387 *
1388 * @param column the cell column
1389 * @param row the cell row
1390 * @return the cell
1391 */
1392 public Cell getCell(final int column, final int row) {
1393 if ((column < 0) || (column > columns.size() - 1)) {
1394 throw new IndexOutOfBoundsException("Column count is " +
1395 columns.size() + ", requested index " + column);
1396 }
1397 if ((row < 0) || (row > rows.size() - 1)) {
1398 throw new IndexOutOfBoundsException("Row count is " +
1399 rows.size() + ", requested index " + row);
1400 }
1401 return rows.get(row).get(column);
1402 }
1403
1404 /**
1405 * Get the text of a particular cell.
1406 *
1407 * @param column the cell column
1408 * @param row the cell row
1409 * @return the text in the cell
1410 */
1411 public String getCellText(final int column, final int row) {
1412 if ((column < 0) || (column > columns.size() - 1)) {
1413 throw new IndexOutOfBoundsException("Column count is " +
1414 columns.size() + ", requested index " + column);
1415 }
1416 if ((row < 0) || (row > rows.size() - 1)) {
1417 throw new IndexOutOfBoundsException("Row count is " +
1418 rows.size() + ", requested index " + row);
1419 }
1420 return rows.get(row).get(column).getText();
1421 }
1422
1423 /**
1424 * Set the text of a particular cell.
1425 *
1426 * @param column the cell column
1427 * @param row the cell row
1428 * @param text the text to put into the cell
1429 */
1430 public void setCellText(final int column, final int row,
1431 final String text) {
1432
1433 if ((column < 0) || (column > columns.size() - 1)) {
1434 throw new IndexOutOfBoundsException("Column count is " +
1435 columns.size() + ", requested index " + column);
1436 }
1437 if ((row < 0) || (row > rows.size() - 1)) {
1438 throw new IndexOutOfBoundsException("Row count is " +
1439 rows.size() + ", requested index " + row);
1440 }
1441 rows.get(row).get(column).setText(text);
1442 }
1443
1444 /**
1445 * Set the action to perform when the user presses enter on a particular
1446 * cell.
1447 *
1448 * @param column the cell column
1449 * @param row the cell row
1450 * @param action the action to perform when the user presses enter on the
1451 * cell
1452 */
1453 public void setCellEnterAction(final int column, final int row,
1454 final TAction action) {
1455
1456 if ((column < 0) || (column > columns.size() - 1)) {
1457 throw new IndexOutOfBoundsException("Column count is " +
1458 columns.size() + ", requested index " + column);
1459 }
1460 if ((row < 0) || (row > rows.size() - 1)) {
1461 throw new IndexOutOfBoundsException("Row count is " +
1462 rows.size() + ", requested index " + row);
1463 }
1464 rows.get(row).get(column).field.setEnterAction(action);
1465 }
1466
1467 /**
1468 * Set the action to perform when the user updates a particular cell.
1469 *
1470 * @param column the cell column
1471 * @param row the cell row
1472 * @param action the action to perform when the user updates the cell
1473 */
1474 public void setCellUpdateAction(final int column, final int row,
1475 final TAction action) {
1476
1477 if ((column < 0) || (column > columns.size() - 1)) {
1478 throw new IndexOutOfBoundsException("Column count is " +
1479 columns.size() + ", requested index " + column);
1480 }
1481 if ((row < 0) || (row > rows.size() - 1)) {
1482 throw new IndexOutOfBoundsException("Row count is " +
1483 rows.size() + ", requested index " + row);
1484 }
1485 rows.get(row).get(column).field.setUpdateAction(action);
1486 }
1487
1488 /**
1489 * Get the width of a column.
1490 *
1491 * @param column the column number
1492 * @return the width of the column
1493 */
1494 public int getColumnWidth(final int column) {
1495 if ((column < 0) || (column > columns.size() - 1)) {
1496 throw new IndexOutOfBoundsException("Column count is " +
1497 columns.size() + ", requested index " + column);
1498 }
1499 return columns.get(column).width;
1500 }
1501
1502 /**
1503 * Set the width of a column.
1504 *
1505 * @param column the column number
1506 * @param width the new width of the column
1507 */
1508 public void setColumnWidth(final int column, final int width) {
1509 if ((column < 0) || (column > columns.size() - 1)) {
1510 throw new IndexOutOfBoundsException("Column count is " +
1511 columns.size() + ", requested index " + column);
1512 }
f528c340
KL
1513
1514 if (width < 4) {
1515 // Columns may not be smaller than 4 cells wide.
1516 return;
1517 }
1518
1519 int delta = width - columns.get(column).width;
2b427404 1520 columns.get(column).width = width;
f528c340
KL
1521 for (Cell cell: columns.get(column).cells) {
1522 cell.setWidth(columns.get(column).width);
1523 cell.field.setWidth(columns.get(column).width);
1524 }
1525 for (int i = column + 1; i < columns.size(); i++) {
1526 columns.get(i).setX(columns.get(i).getX() + delta);
1527 }
1528 if (column == columns.size() - 1) {
1529 bottomRightCorner();
1530 } else {
1531 alignGrid();
1532 }
2b427404
KL
1533 }
1534
1535 /**
1536 * Get the label of a column.
1537 *
1538 * @param column the column number
1539 * @return the label of the column
1540 */
1541 public String getColumnLabel(final int column) {
1542 if ((column < 0) || (column > columns.size() - 1)) {
1543 throw new IndexOutOfBoundsException("Column count is " +
1544 columns.size() + ", requested index " + column);
1545 }
1546 return columns.get(column).label;
1547 }
1548
1549 /**
1550 * Set the label of a column.
1551 *
1552 * @param column the column number
1553 * @param label the new label of the column
1554 */
1555 public void setColumnLabel(final int column, final String label) {
1556 if ((column < 0) || (column > columns.size() - 1)) {
1557 throw new IndexOutOfBoundsException("Column count is " +
1558 columns.size() + ", requested index " + column);
1559 }
1560 columns.get(column).label = label;
1561 }
1562
1563 /**
1564 * Get the label of a row.
1565 *
1566 * @param row the row number
1567 * @return the label of the row
1568 */
1569 public String getRowLabel(final int row) {
1570 if ((row < 0) || (row > rows.size() - 1)) {
1571 throw new IndexOutOfBoundsException("Row count is " +
1572 rows.size() + ", requested index " + row);
1573 }
1574 return rows.get(row).label;
1575 }
1576
1577 /**
1578 * Set the label of a row.
1579 *
1580 * @param row the row number
1581 * @param label the new label of the row
1582 */
1583 public void setRowLabel(final int row, final String label) {
1584 if ((row < 0) || (row > rows.size() - 1)) {
1585 throw new IndexOutOfBoundsException("Row count is " +
1586 rows.size() + ", requested index " + row);
1587 }
1588 rows.get(row).label = label;
1589 }
1590
1591 /**
1592 * Insert one row at a particular index.
1593 *
1594 * @param idx the row number
1595 */
1596 private void insertRowAt(final int idx) {
1597 Row newRow = new Row(idx);
1598 for (int i = 0; i < columns.size(); i++) {
1599 Cell cell = new Cell(this, columns.get(i).getX(),
1600 rows.get(selectedRow).getY(), COLUMN_DEFAULT_WIDTH, 1, i, idx);
1601 newRow.add(cell);
1602 columns.get(i).cells.add(idx, cell);
1603 }
1604 rows.add(idx, newRow);
1605
1606 for (int x = 0; x < columns.size(); x++) {
1607 for (int y = idx; y < rows.size(); y++) {
1608 columns.get(x).get(y).row = y;
1609 columns.get(x).get(y).column = x;
1610 }
1611 }
1612 for (int i = idx + 1; i < rows.size(); i++) {
1613 String oldRowLabel = Integer.toString(i - 1);
1614 if (rows.get(i).label.equals(oldRowLabel)) {
1615 rows.get(i).label = Integer.toString(i);
1616 }
1617 }
1618 alignGrid();
1619 }
1620
1621 /**
1622 * Insert one row above a particular row.
1623 *
1624 * @param row the row number
1625 */
1626 public void insertRowAbove(final int row) {
1627 if ((row < 0) || (row > rows.size() - 1)) {
1628 throw new IndexOutOfBoundsException("Row count is " +
1629 rows.size() + ", requested index " + row);
1630 }
1631 insertRowAt(selectedRow);
1632 selectedRow++;
f528c340 1633 activate(columns.get(selectedColumn).get(selectedRow));
2b427404
KL
1634 }
1635
1636 /**
1637 * Insert one row below a particular row.
1638 *
1639 * @param row the row number
1640 */
1641 public void insertRowBelow(final int row) {
1642 if ((row < 0) || (row > rows.size() - 1)) {
1643 throw new IndexOutOfBoundsException("Row count is " +
1644 rows.size() + ", requested index " + row);
1645 }
1646 int idx = selectedRow + 1;
1647 if (idx < rows.size()) {
1648 insertRowAt(idx);
f528c340 1649 activate(columns.get(selectedColumn).get(selectedRow));
2b427404
KL
1650 return;
1651 }
1652
1653 // selectedRow is the last row, we need to perform an append.
1654 Row newRow = new Row(idx);
1655 for (int i = 0; i < columns.size(); i++) {
1656 Cell cell = new Cell(this, columns.get(i).getX(),
1657 rows.get(selectedRow).getY(), COLUMN_DEFAULT_WIDTH, 1, i, idx);
1658 newRow.add(cell);
1659 columns.get(i).cells.add(cell);
1660 }
1661 rows.add(newRow);
1662 alignGrid();
f528c340 1663 activate(columns.get(selectedColumn).get(selectedRow));
2b427404
KL
1664 }
1665
1666 /**
1667 * Delete a particular row.
1668 *
1669 * @param row the row number
1670 */
1671 public void deleteRow(final int row) {
1672 if ((row < 0) || (row > rows.size() - 1)) {
1673 throw new IndexOutOfBoundsException("Row count is " +
1674 rows.size() + ", requested index " + row);
1675 }
ad584966
KL
1676 if (rows.size() == 1) {
1677 // Don't delete the last row.
1678 return;
1679 }
1680 for (int i = 0; i < columns.size(); i++) {
1681 Cell cell = columns.get(i).cells.remove(row);
1682 getChildren().remove(cell);
1683 }
1684 rows.remove(row);
1685
1686 for (int x = 0; x < columns.size(); x++) {
1687 for (int y = row; y < rows.size(); y++) {
1688 columns.get(x).get(y).row = y;
1689 columns.get(x).get(y).column = x;
1690 }
1691 }
1692 for (int i = row; i < rows.size(); i++) {
1693 String oldRowLabel = Integer.toString(i + 1);
1694 if (rows.get(i).label.equals(oldRowLabel)) {
1695 rows.get(i).label = Integer.toString(i);
1696 }
1697 }
1698 if (selectedRow == rows.size()) {
1699 selectedRow--;
1700 }
f528c340 1701 activate(columns.get(selectedColumn).get(selectedRow));
ab8b0034 1702 bottomRightCorner();
2b427404
KL
1703 }
1704
1705 /**
1706 * Insert one column at a particular index.
1707 *
1708 * @param idx the column number
1709 */
1710 private void insertColumnAt(final int idx) {
1711 Column newColumn = new Column(idx);
1712 for (int i = 0; i < rows.size(); i++) {
1713 Cell cell = new Cell(this, columns.get(selectedColumn).getX(),
1714 rows.get(i).getY(), COLUMN_DEFAULT_WIDTH, 1, idx, i);
1715 newColumn.add(cell);
1716 rows.get(i).cells.add(idx, cell);
1717 }
1718 columns.add(idx, newColumn);
1719
1720 for (int x = idx; x < columns.size(); x++) {
1721 for (int y = 0; y < rows.size(); y++) {
1722 columns.get(x).get(y).row = y;
1723 columns.get(x).get(y).column = x;
1724 }
1725 }
1726 for (int i = idx + 1; i < columns.size(); i++) {
1727 String oldColumnLabel = makeColumnLabel(i - 1);
1728 if (columns.get(i).label.equals(oldColumnLabel)) {
1729 columns.get(i).label = makeColumnLabel(i);
1730 }
1731 }
1732 alignGrid();
1733 }
1734
1735 /**
1736 * Insert one column to the left of a particular column.
1737 *
1738 * @param column the column number
1739 */
1740 public void insertColumnLeft(final int column) {
1741 if ((column < 0) || (column > columns.size() - 1)) {
1742 throw new IndexOutOfBoundsException("Column count is " +
1743 columns.size() + ", requested index " + column);
1744 }
1745 insertColumnAt(selectedColumn);
1746 selectedColumn++;
f528c340 1747 activate(columns.get(selectedColumn).get(selectedRow));
2b427404
KL
1748 }
1749
1750 /**
1751 * Insert one column to the right of a particular column.
1752 *
1753 * @param column the column number
1754 */
1755 public void insertColumnRight(final int column) {
1756 if ((column < 0) || (column > columns.size() - 1)) {
1757 throw new IndexOutOfBoundsException("Column count is " +
1758 columns.size() + ", requested index " + column);
1759 }
1760 int idx = selectedColumn + 1;
1761 if (idx < columns.size()) {
1762 insertColumnAt(idx);
f528c340 1763 activate(columns.get(selectedColumn).get(selectedRow));
2b427404
KL
1764 return;
1765 }
1766
1767 // selectedColumn is the last column, we need to perform an append.
1768 Column newColumn = new Column(idx);
1769 for (int i = 0; i < rows.size(); i++) {
1770 Cell cell = new Cell(this, columns.get(selectedColumn).getX(),
1771 rows.get(i).getY(), COLUMN_DEFAULT_WIDTH, 1, idx, i);
1772 newColumn.add(cell);
1773 rows.get(i).cells.add(cell);
1774 }
1775 columns.add(newColumn);
1776 alignGrid();
f528c340 1777 activate(columns.get(selectedColumn).get(selectedRow));
2b427404
KL
1778 }
1779
1780 /**
1781 * Delete a particular column.
1782 *
1783 * @param column the column number
1784 */
1785 public void deleteColumn(final int column) {
1786 if ((column < 0) || (column > columns.size() - 1)) {
1787 throw new IndexOutOfBoundsException("Column count is " +
1788 columns.size() + ", requested index " + column);
1789 }
ad584966
KL
1790 if (columns.size() == 1) {
1791 // Don't delete the last column.
1792 return;
1793 }
1794 for (int i = 0; i < rows.size(); i++) {
1795 Cell cell = rows.get(i).cells.remove(column);
1796 getChildren().remove(cell);
1797 }
1798 columns.remove(column);
1799
1800 for (int x = column; x < columns.size(); x++) {
1801 for (int y = 0; y < rows.size(); y++) {
1802 columns.get(x).get(y).row = y;
1803 columns.get(x).get(y).column = x;
1804 }
1805 }
1806 for (int i = column; i < columns.size(); i++) {
1807 String oldColumnLabel = makeColumnLabel(i + 1);
1808 if (columns.get(i).label.equals(oldColumnLabel)) {
1809 columns.get(i).label = makeColumnLabel(i);
1810 }
1811 }
1812 if (selectedColumn == columns.size()) {
1813 selectedColumn--;
1814 }
f528c340 1815 activate(columns.get(selectedColumn).get(selectedRow));
ab8b0034 1816 bottomRightCorner();
2b427404
KL
1817 }
1818
1819 /**
1820 * Delete the selected cell, shifting cells over to the left.
1821 */
1822 public void deleteCellShiftLeft() {
ab8b0034
KL
1823 // All we do is copy the text from every cell in this row over.
1824 for (int i = selectedColumn + 1; i < columns.size(); i++) {
1825 setCellText(i - 1, selectedRow, getCellText(i, selectedRow));
1826 }
1827 setCellText(columns.size() - 1, selectedRow, "");
2b427404
KL
1828 }
1829
1830 /**
1831 * Delete the selected cell, shifting cells from below up.
1832 */
1833 public void deleteCellShiftUp() {
ab8b0034
KL
1834 // All we do is copy the text from every cell in this column up.
1835 for (int i = selectedRow + 1; i < rows.size(); i++) {
1836 setCellText(selectedColumn, i - 1, getCellText(selectedColumn, i));
1837 }
1838 setCellText(selectedColumn, rows.size() - 1, "");
2b427404
KL
1839 }
1840
1841 /**
1842 * Set a particular cell read-only (non-editable) or not.
1843 *
1844 * @param column the cell column
1845 * @param row the cell row
1846 * @param readOnly if true, the cell will be non-editable
1847 */
1848 public void setCellReadOnly(final int column, final int row,
1849 final boolean readOnly) {
1850
1851 if ((column < 0) || (column > columns.size() - 1)) {
1852 throw new IndexOutOfBoundsException("Column count is " +
1853 columns.size() + ", requested index " + column);
1854 }
1855 if ((row < 0) || (row > rows.size() - 1)) {
1856 throw new IndexOutOfBoundsException("Row count is " +
1857 rows.size() + ", requested index " + row);
1858 }
1859 rows.get(row).get(column).setReadOnly(readOnly);
1860 }
1861
1862 /**
1863 * Set an entire row of cells read-only (non-editable) or not.
1864 *
1865 * @param row the row number
1866 * @param readOnly if true, the cells will be non-editable
1867 */
1868 public void setRowReadOnly(final int row, final boolean readOnly) {
1869 if ((row < 0) || (row > rows.size() - 1)) {
1870 throw new IndexOutOfBoundsException("Row count is " +
1871 rows.size() + ", requested index " + row);
1872 }
1873 for (Cell cell: rows.get(row).cells) {
1874 cell.setReadOnly(readOnly);
1875 }
1876 }
1877
1878 /**
1879 * Set an entire column of cells read-only (non-editable) or not.
1880 *
1881 * @param column the column number
1882 * @param readOnly if true, the cells will be non-editable
1883 */
1884 public void setColumnReadOnly(final int column, final boolean readOnly) {
1885 if ((column < 0) || (column > columns.size() - 1)) {
1886 throw new IndexOutOfBoundsException("Column count is " +
1887 columns.size() + ", requested index " + column);
1888 }
1889 for (Cell cell: columns.get(column).cells) {
1890 cell.setReadOnly(readOnly);
1891 }
1892 }
1893
f528c340
KL
1894 /**
1895 * Set all borders across the entire table to Border.NONE.
1896 */
1897 public void setBorderAllNone() {
1898 topBorder = Border.NONE;
1899 leftBorder = Border.NONE;
1900 for (int i = 0; i < columns.size(); i++) {
1901 columns.get(i).rightBorder = Border.NONE;
1902 }
1903 for (int i = 0; i < rows.size(); i++) {
1904 rows.get(i).bottomBorder = Border.NONE;
1905 rows.get(i).height = 1;
1906 }
1907 bottomRightCorner();
1908 }
1909
1910 /**
1911 * Set all borders across the entire table to Border.SINGLE.
1912 */
1913 public void setBorderAllSingle() {
1914 topBorder = Border.SINGLE;
1915 leftBorder = Border.SINGLE;
1916 for (int i = 0; i < columns.size(); i++) {
1917 columns.get(i).rightBorder = Border.SINGLE;
1918 }
1919 for (int i = 0; i < rows.size(); i++) {
1920 rows.get(i).bottomBorder = Border.SINGLE;
1921 rows.get(i).height = 2;
1922 }
1923 alignGrid();
1924 }
1925
1926 /**
1927 * Set all borders around the selected cell to Border.NONE.
1928 */
1929 public void setBorderCellNone() {
1930 if (selectedRow == 0) {
1931 topBorder = Border.NONE;
1932 }
1933 if (selectedColumn == 0) {
1934 leftBorder = Border.NONE;
1935 }
1936 columns.get(selectedColumn).rightBorder = Border.NONE;
1937 rows.get(selectedRow).bottomBorder = Border.NONE;
1938 rows.get(selectedRow).height = 1;
1939 bottomRightCorner();
1940 }
1941
1942 /**
1943 * Set all borders around the selected cell to Border.SINGLE.
1944 */
1945 public void setBorderCellSingle() {
1946 if (selectedRow == 0) {
1947 topBorder = Border.SINGLE;
1948 }
1949 if (selectedColumn == 0) {
1950 leftBorder = Border.SINGLE;
1951 }
1952 columns.get(selectedColumn).rightBorder = Border.SINGLE;
1953 rows.get(selectedRow).bottomBorder = Border.SINGLE;
1954 rows.get(selectedRow).height = 2;
1955 alignGrid();
1956 }
1957
1958 /**
1959 * Set the column border to the right of the selected cell to
1960 * Border.SINGLE.
1961 */
1962 public void setBorderColumnRightSingle() {
1963 columns.get(selectedColumn).rightBorder = Border.SINGLE;
1964 alignGrid();
1965 }
1966
1967 /**
1968 * Set the column border to the right of the selected cell to
1969 * Border.SINGLE.
1970 */
1971 public void setBorderColumnLeftSingle() {
1972 if (selectedColumn == 0) {
1973 leftBorder = Border.SINGLE;
1974 } else {
1975 columns.get(selectedColumn - 1).rightBorder = Border.SINGLE;
1976 }
1977 alignGrid();
1978 }
1979
1980 /**
1981 * Set the row border above the selected cell to Border.SINGLE.
1982 */
1983 public void setBorderRowAboveSingle() {
1984 if (selectedRow == 0) {
1985 topBorder = Border.SINGLE;
1986 } else {
1987 rows.get(selectedRow - 1).bottomBorder = Border.SINGLE;
1988 rows.get(selectedRow - 1).height = 2;
1989 }
1990 alignGrid();
1991 }
1992
1993 /**
1994 * Set the row border below the selected cell to Border.SINGLE.
1995 */
1996 public void setBorderRowBelowSingle() {
1997 rows.get(selectedRow).bottomBorder = Border.SINGLE;
1998 rows.get(selectedRow).height = 2;
1999 alignGrid();
2000 }
2001
2002 /**
2003 * Set the row border below the selected cell to Border.DOUBLE.
2004 */
2005 public void setBorderRowBelowDouble() {
2006 rows.get(selectedRow).bottomBorder = Border.DOUBLE;
2007 rows.get(selectedRow).height = 2;
2008 alignGrid();
2009 }
2010
2011 /**
2012 * Set the row border below the selected cell to Border.THICK.
2013 */
2014 public void setBorderRowBelowThick() {
2015 rows.get(selectedRow).bottomBorder = Border.THICK;
2016 rows.get(selectedRow).height = 2;
2017 alignGrid();
2018 }
2019
1dac6b8d 2020}