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